Skip to content

Keep inline citation links attached while wrapping (#325) - #348

Merged
leynos merged 9 commits into
mainfrom
issue-325-keep-inline-citation-links-attached-while-wrapping
Jun 7, 2026
Merged

Keep inline citation links attached while wrapping (#325)#348
leynos merged 9 commits into
mainfrom
issue-325-keep-inline-citation-links-attached-while-wrapping

Conversation

@leynos

@leynos leynos commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

This branch keeps parenthesised inline citation links attached while prose is wrapped. It treats word([1](url)) and adjacent citation groups as atomic wrapping spans, so the formatter no longer leaves bare opening parentheses at line ends or emits standalone )( fragments.

Closes #325.

Review walkthrough

Validation

  • cargo test --all-targets --all-features wrap::tests: passed.
  • make check-fmt: passed.
  • make lint: passed.
  • make test: passed.
  • coderabbit review --agent: attempted three times; each attempt connected and stalled at preparing_sandbox without findings, rate-limit output, or completion.

Notes

The local make fmt target also attempted Markdown formatting, but that generated unrelated documentation churn. Those generated Markdown changes were discarded before the committed branch was pushed.

Summary by Sourcery

Ensure inline citation-style links and their surrounding punctuation are treated as atomic spans during wrapping to avoid awkward line breaks.

Bug Fixes:

  • Prevent line wrapping from leaving opening parentheses or adjacent citation punctuation (e.g., ")(") orphaned around inline links.

Enhancements:

  • Adjust inline tokenization to split before "([" and to treat opener-followed links and trailing punctuation as a single span when wrapping.

Tests:

  • Add regression tests for token grouping and inline wrapping of parenthesised inline links and adjacent citation-style references.

Treat parenthesised inline citation links as one wrapping span so prose
such as `word([1](url))` cannot split after the opening parenthesis.
Keep adjacent citation groups together as well, avoiding orphaned `)(`
fragments in wrapped Markdown source.

Add tokenizer, span grouping, and wrapping regressions for single and
adjacent inline citation links.
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@lodyai[bot], we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 19 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b4a3fa7-6c22-4d46-9450-622bbd6ddaae

📥 Commits

Reviewing files that changed from the base of the PR and between 85e90b1 and cec742b.

📒 Files selected for processing (3)
  • docs/developers-guide.md
  • src/wrap/tests/span_grouping_props.rs
  • tests/wrap/cli.rs

Walkthrough

Extend tokenisation and span-coupling to detect opener + inline-link patterns (e.g. word(1)), group them as a single SpanKind::Link including trailing punctuation, and add tests and docs to ensure wrapping preserves these parenthesised citations.

Changes

Inline citation link coupling during wrapping

Layer / File(s) Summary
Tokenisation boundary detection for inline citations
src/wrap/tokenize/mod.rs
Tokenisation now stops trailing-punctuation and plain-text scanning at inline citation starts (([), uses escape-context checks to distinguish legitimate citations from escaped brackets, and breaks when citations are detected to prevent splitting them across tokens.
Span coupling for opener-followed inline links
src/wrap/inline/span_helpers.rs
New helper try_couple_inline_link_after_opener and an added is_opening_punct import detect and couple patterns where an opening punctuation token precedes an inline link, update span width to include both tokens, and extend the span to cover trailing punctuation after the link.
Span determination integration
src/wrap/inline.rs
New coupling helper is imported and invoked during the determine_token_span forward-coupling loop, allowing span kind and end index to be updated when citation patterns are detected.
Test coverage for citation wrapping and coupling
src/wrap/tests/inline_wrapping.rs, src/wrap/tests/token_grouping.rs, src/wrap/tests/span_grouping_props.rs, tests/wrap/cli.rs
New tests generate inline-citation chains, assert invariants after wrapping, add parametric checks for determine_token_span across citation openers, extend token matrices for parenthesised embedded links, ensure escaped link literals remain intact, and add CLI-level checks.
Docs and README
README.md, docs/developers-guide.md, docs/users-guide.md
User and developer documentation updated to describe the new opener-coupling behaviour and to list parenthesised inline citations as preserved by the wrap engine.

Possibly related PRs

  • leynos/mdtablefix#295: Both PRs modify the inline wrapping span-grouping logic in src/wrap/inline.rs to forward-couple opener punctuation with the following atomic inline fragment (retrieved: inline code spans; main: inline link/citation tokens via try_couple_inline_link_after_opener).
  • leynos/mdtablefix#301: Both PRs change the inline span-grouping pipeline around determine_token_span and src/wrap/inline/span_helpers.rs, touching ordering and coupling interactions between atomic spans.
  • leynos/mdtablefix#280: Both PRs modify src/wrap/inline.rs’s span-coupling in determine_token_span, with the main PR adding a new “inline link after opener” coupling step immediately before the existing footnote-reference coupling logic.

Poem

Parentheses cling where once they strayed,
Citations and words now line up stayed,
No orphaned )( to mar the view,
Links and openers stick like glue,
Wraps stay tidy — source and diff renewed.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (3 errors, 4 warnings)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Three test helper functions lack doc comments explicitly requested in review: citation_link_starts, inline_citation_strategy, and grouped_spans. Add doc comments to citation_link_starts (tests/wrap/cli.rs), inline_citation_strategy and grouped_spans (src/wrap/tests/span_grouping_props.rs).
Unit Architecture ❌ Error Test helper functions lack documentation: citation_link_starts(), grouped_spans(), and inline_citation_strategy() have no doc comments explaining their responsibilities. Add doc comments to test helpers explaining their roles in the test suite, citing PR review requirements for documentation of unit responsibilities and dependencies.
Rust Compiler Lint Integrity ❌ Error Three new test helper functions lack doc comments requested in review: citation_link_starts, inline_citation_strategy, and grouped_spans. No lint suppressions or unused code detected. Add doc comments to citation_link_starts, inline_citation_strategy, and grouped_spans explaining their purpose and usage in test assertions.
Developer Documentation ⚠️ Warning try_couple_inline_link_after_opener is documented, but architectural changes to segment_inline tokenisation for citation-aware punctuation scanning are not documented. Document in developers-guide.md how segment_inline stops scanning trailing punctuation at "([" to support citation atomicity during wrapping.
Testing (Unit And Behavioural) ⚠️ Warning Test helper functions citation_link_starts, grouped_spans, and inline_citation_strategy lack documentation despite review requests specifically asking for doc comments on these functions. Add doc comments to the three test helper functions as originally requested in the code review comments to explain their purpose and role.
Testing (Property / Proof) ⚠️ Warning Line-level wrapping invariants (no stranded parens, citation preservation, no orphaned fragments) vary over input ranges but are only tested with 2 hand-written unit test cases, not property tests. Add property tests covering line-level wrapping invariants with parametrised widths and generative citation/context strategies.
Architectural Complexity And Maintainability ⚠️ Warning Test helpers lack doc comments: citation_link_starts, grouped_spans, and inline_citation_strategy need documentation per review comments. Add doc comments to all three test helper functions explaining purpose and usage as requested in the review feedback.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Keep inline citation links attached while wrapping (#325)' clearly and accurately summarises the main change: preventing wrapping from detaching inline citation links and citations from their context.
Description check ✅ Passed The description comprehensively covers the changeset, explaining the problem statement, solution approach, review walkthrough, validation steps, and outstanding review interactions.
Linked Issues check ✅ Passed The PR fulfils all coding objectives from issue #325: tokenisation is adjusted to split before '([', inline citation links are coupled into atomic spans, and regression tests cover single and adjacent citation scenarios.
Out of Scope Changes check ✅ Passed All changes target the inline citation wrapping problem scoped to issue #325. Documentation updates clarify the new behaviour, test additions ensure regression coverage, and no unrelated code modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
User-Facing Documentation ✅ Passed Users-guide documents parenthesised inline citations as unbreakable units. README signposts the feature. Behavioural improvement, not breaking change.
Module-Level Documentation ✅ Passed All 7 modified/added modules contain clear, comprehensive module-level docstrings explaining purpose, utility, function, and relationships to other components as required.
Testing (Compile-Time / Ui) ✅ Passed Two inline snapshots meaningfully encode citation atomicity and punctuation handling; trybuild unnecessary given no compile-time features; snapshots are focused and deterministic.
Domain Architecture ✅ Passed Domain logic properly segregated from infrastructure; predicates encapsulate Markdown syntax; span coupling logic expresses pure domain concepts without HTTP/ORM/filesystem concerns.
Observability ✅ Passed Not applicable: this PR is an algorithmic fix to text wrapping with no operational characteristics requiring logging, metrics, tracing, or alerts. Test coverage validates correctness.
Security And Privacy ✅ Passed No secrets, unsafe code, injection risks, or data exposure introduced. Test fixtures use example.com. Input validation is safe. Functions properly scoped.
Performance And Resource Use ✅ Passed Characters iterated once per token in bounded streams, zero-copy references, no allocations in hot paths, no unbounded resource growth or regressions.
Concurrency And State ✅ Passed No concurrency, async, or shared mutable state introduced; all changes use locally-scoped immutable data and stack-allocated mutable state within synchronous single-threaded text processing.
📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #325

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-325-keep-inline-citation-links-attached-while-wrapping

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

@sourcery-ai

sourcery-ai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adjusts inline tokenization and span grouping so that parenthesised inline citation links (including adjacent citation groups) are treated as atomic wrapping spans, preventing stranded parentheses and orphaned ")(" fragments when wrapping prose, and adds regression tests for these cases.

File-Level Changes

Change Details Files
Refine inline tokenization to specially handle parenthesised citation link patterns and avoid splitting opener-plus-link sequences into awkward punctuation tokens.
  • Replace generic trailing-punctuation scanning after links with a custom scan_trailing_punctuation_end that stops before a "([" citation opener sequence.
  • Adjust scan_plain_text_end to break before a "([" pattern so the opening parenthesis becomes its own token ahead of a link.
  • Extend should_stop_plain_text so that an unescaped "(" directly followed by "[" is treated as a terminator, similar to link/image starts.
  • Add test coverage to assert that segment_inline splits inputs like "word(link)" into separate opener and link tokens while preserving grouping semantics for wrapping.
src/wrap/tokenize/mod.rs
src/wrap/tests/token_grouping.rs
Extend span determination to couple opener tokens followed by inline links into a single atomic span for wrapping, including any trailing punctuation.
  • Introduce try_couple_inline_link_after_opener helper that, when an opening-punctuation token is immediately followed by a link-like token, merges them into a SpanKind::Link and absorbs trailing punctuation.
  • Invoke try_couple_inline_link_after_opener early in determine_token_span so opener-plus-link citations are grouped before other span-coupling logic (e.g., footnotes, chaining).
  • Add regression tests verifying that token spans for inputs like "word(link)" and adjacent citations group the citation segment(s) as a single span starting at the opener.
src/wrap/inline/span_helpers.rs
src/wrap/inline.rs
src/wrap/tests/token_grouping.rs
Ensure wrapping keeps inline citation links attached to the preceding word and avoids visually broken citation fragments across lines.
  • Add wrap_preserving_code_keeps_inline_citation_links_attached test that asserts wrapped output retains the full citation sequence on a single line and does not leave an opening parenthesis at line end.
  • Within the new test, assert that citation links do not start a continuation line and that lines never contain a standalone ")(" token, covering both single and adjacent citation scenarios.
src/wrap/tests/inline_wrapping.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#325 Ensure the prose wrapper treats inline citation patterns like word([n](url)) (including adjacent citations such as word([1](url))([2](url2))) as atomic units so that wrapping does not strand opening parentheses at line ends or create orphaned )( fragments on separate lines.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@leynos
leynos marked this pull request as ready for review June 6, 2026 07:52
sourcery-ai[bot]

This comment was marked as resolved.

Centralize detection of parenthesised inline citation starts so tokenizer
call sites use one shared rule. Preserve escaped `\([link](url))`
sequences as plain text instead of treating their link label as structural
Markdown.

Derive citation link markers in wrapping regressions and add negative span
cases to guard against coupling from non-opener tokens.
@coderabbitai coderabbitai Bot added the Issue label Jun 6, 2026
@leynos

leynos commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following now been resolved?

Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity.

Please address the comments from this code review:

## Overall Comments
- The `scan_trailing_punctuation_end`, `scan_plain_text_end`, and `should_stop_plain_text` functions now all embed slightly different `([`/`(` + `[` detection logic; consider extracting a shared helper (e.g., `starts_inline_citation(text, index)`) so the conditions stay consistent and easier to reason about.
- In `wrap_preserving_code_keeps_inline_citation_links_attached`, the checks for `[1](`, `[6](`, and `[7](` are hard-coded; deriving the citation markers from `expected_citation` (or parameterizing them) would make the test less brittle and easier to extend to additional cases.

## Individual Comments

### Comment 1
<location path="src/wrap/tokenize/mod.rs" line_range="151-152" />
<code_context>
 }

 fn scan_plain_text_end(text: &str, bytes: &[u8], mut index: usize) -> usize {
+    if text[index..].starts_with("([") {
+        return index + 1;
+    }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** The special-case for "([" bypasses existing escape handling and may mis-handle escaped sequences.

This early return runs before the main loop and before any `is_escaped` logic that uses `bytes`, so an input like `\([` will still trigger a split as if it were an unescaped structural marker. If that’s unintended, please move this check into `should_stop_plain_text` (where escape state is known) or only return early when the sequence is not escaped.
</issue_to_address>

### Comment 2
<location path="src/wrap/tests/token_grouping.rs" line_range="42-39" />
<code_context>
     }
 }

+#[rstest]
+#[case(
+    "The formatter keeps pattern([1](https://example.com/ref)) attached while wrapping.",
</code_context>
<issue_to_address>
**suggestion (testing):** Add negative cases to ensure non-opener tokens do not get coupled as citation groups

The current tests only cover positive cases where `start` points at the `(` before an inline link, which is what the new coupling logic targets. Please add at least one negative case where `start` points at a non-opener token (e.g. `start = 0` on `"word([link](url))"` or on a closing `)`), and assert that `determine_token_span` returns only the single token, not a grouped citation. This will help catch future over-coupling regressions if the span logic changes.
</issue_to_address>

### Comment 3
<location path="src/wrap/tokenize/mod.rs" line_range="122" />
<code_context>
     tokens
 }

+fn scan_trailing_punctuation_end(text: &str, mut index: usize) -> usize {
+    while index < text.len() {
+        let Some(current) = text[index..].chars().next() else {
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the special `([` handling into a shared helper function and calling it from all three sites to avoid duplicating the same condition logic.

You can reduce the added complexity by centralizing the `([` rule in a small helper and reusing it in all three places, instead of re‑encoding the same condition each time.

### 1. Introduce a single helper for the `([` case

```rust
fn is_link_label_paren_open(text: &str, index: usize) -> bool {
    text.get(index..).is_some_and(|s| s.starts_with("(["))
}
```

### 2. Use the helper in `scan_trailing_punctuation_end`

```rust
fn scan_trailing_punctuation_end(text: &str, mut index: usize) -> usize {
    while index < text.len() {
        let Some(current) = text[index..].chars().next() else {
            break;
        };

        if is_link_label_paren_open(text, index) {
            break;
        }

        if !is_trailing_punctuation(current) {
            break;
        }

        index += current.len_utf8();
    }
    index
}
```

### 3. Use the helper in `scan_plain_text_end`

```rust
fn scan_plain_text_end(text: &str, bytes: &[u8], mut index: usize) -> usize {
    if is_link_label_paren_open(text, index) {
        return index + 1;
    }

    while index < text.len() {
        let Some(current) = text[index..].chars().next() else {
            break;
        };
        if current.is_whitespace() || current == '`' {
            break;
        }

        let current_escaped = has_odd_backslash_escape_bytes(bytes, index);
        if should_stop_plain_text(text, bytes, index, (current, current_escaped)) {
            break;
        }

        index += current.len_utf8();
    }
    index
}
```

### 4. Use the helper in `should_stop_plain_text`

```rust
fn should_stop_plain_text(
    text: &str,
    bytes: &[u8],
    index: usize,
    current: (char, bool),
) -> bool {
    let (ch, is_escaped) = current;
    if ch == '[' {
        return !is_escaped && !bracket_follows_escaped_bang(bytes, index);
    }
    if ch == '(' {
        return !is_escaped && is_link_label_paren_open(text, index);
    }
    looks_like_image_start(text, index, ch) && !is_escaped
}
```

This keeps all current behavior, but:

- The semantic rule “`([` has special meaning” lives in one place.
- All three call sites share the same abstraction, so future tweaks to the rule are localized.
- `scan_trailing_punctuation_end` stays narrow in purpose and doesn’t carry ad‑hoc logic inline.
</issue_to_address>

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat src/wrap/tokenize/mod.rs

Length of output: 14462


🏁 Script executed:

#!/bin/bash
cat src/wrap/tests/token_grouping.rs

Length of output: 3704


🏁 Script executed:

#!/bin/bash
cat src/wrap/tests/inline_wrapping.rs

Length of output: 12355



@leynos: Yes — all five issues are now resolved. Here is the breakdown:

Overall comment 1 — shared helper for ([ detection
starts_inline_citation(text, index) -> bool is extracted and called from all three sites: scan_trailing_punctuation_end, scan_plain_text_end, and should_stop_plain_text.

Overall comment 2 — hard-coded citation markers
✅ The citation_link_starts(expected_citation) helper now derives markers dynamically from the fixture string; [1](, [6](, and [7]( are no longer hard-coded in the test body.

Comment 1 — escape-bypass bug risk
scan_plain_text_end's early return is now guarded:

if starts_inline_citation(text, index) && !has_odd_backslash_escape_bytes(bytes, index) {
    return index + 1;
}

Escaped sequences such as \([ correctly fall through to the main loop.

Comment 2 — negative test cases
determine_token_span_does_not_overcouple_citation_tokens covers non-opener starts: start = 0 on a plain word, start = 2 at the link token itself, and start = 3 at the closing ).

Comment 3 — complexity / centralised helper
✅ Covered by the same starts_inline_citation helper noted above; all three call sites share a single definition.

[resolve]


🧠 Learnings used
Learnt from: CR
Repo: leynos/wireframe PR: 0
File: docs/roadmap.md:0-0
Timestamp: 2026-06-06T22:34:47.042Z
Learning: Applies to docs/**/*.md : Capture any intentionally deferred `Vec<u8>` surfaces, together with explicit rationale and the next review point

Learnt from: CR
Repo: leynos/wireframe PR: 0
File: docs/formal-verification-methods-in-wireframe.md:0-0
Timestamp: 2026-06-06T22:32:36.740Z
Learning: Applies to docs/src/{frame,fragment}/**/*.rs : First Kani targets in Phase 2: supported length-prefix round-trip (widths 1,2,4,8 with both endiannesses), unsupported-width behavior regression harness, `FragmentSeries` trace checks, `Reassembler` small-trace checks, `MessageSeries` sequencing checks

@leynos

leynos commented Jun 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

Document parenthesised inline citation coupling in user-facing and
developer-facing guides. Extend the inline wrapping tests with generated
citation chains across wrap widths, prefix lengths, and citation counts,
plus inline snapshots for representative wrapped output.

Keep the existing helper structure because review verification found the
proposed inlining would make the tokenizer and span grouping callers
noisier without simplifying the rule.

@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: 3

🤖 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 `@docs/developers-guide.md`:
- Line 227: Replace the British spelling "parenthesised" with the Oxford-style
"parenthesized" in the docs string shown (the occurrence of the token
"parenthesised" in developers-guide.md) so the documentation follows the
repository's en-GB-oxendict Oxford "-ize" convention; update that single word
occurrence to "parenthesized" and run a quick grep for "parenthesised" to ensure
no other instances remain.

In `@docs/users-guide.md`:
- Line 42: Replace the spelling "Parenthesised" with the Oxford-style
"Parenthesized" in the sentence containing the example `pattern([1](url))` (the
heading/sentence on Line 42) so the docs follow the enforced en-GB oxendict
`-ize` house style.

In `@README.md`:
- Around line 11-12: Replace the British spelling "parenthesised" with the
Oxford -ize form "parenthesized" in the README text that mentions examples like
`pattern([1](url))`; update the single word occurrence in the sentence
containing "footnote references, parenthesised inline citations" to conform to
the en-GB-oxendict rule and preserve surrounding punctuation and formatting.
🪄 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: 3659c165-b7fc-45a9-aed4-8898cbae6bd8

📥 Commits

Reviewing files that changed from the base of the PR and between 8b79519 and 37b4ad5.

📒 Files selected for processing (8)
  • README.md
  • docs/developers-guide.md
  • docs/users-guide.md
  • src/wrap/inline.rs
  • src/wrap/inline/span_helpers.rs
  • src/wrap/tests/inline_wrapping.rs
  • src/wrap/tests/token_grouping.rs
  • src/wrap/tokenize/mod.rs

Comment thread docs/developers-guide.md Outdated
Comment thread docs/users-guide.md Outdated
Comment thread README.md Outdated
Normalize the inline citation documentation from `parenthesised` to the
Oxford-style `parenthesized` spelling required by the project docs style.
@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

Describe the inline wrapping and token grouping test modules in the same
multi-line style as the fence tracker tests. Capture the components under
test, the behaviours covered, and how issue #325 citation coupling fits the
inline wrapping pipeline.
Exercise parenthesized inline citation wrapping through the `mdtablefix
--wrap` command-line path. Cover both a single GitHub-style citation and an
adjacent citation chain so regressions cannot hide behind internal-only tests.
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Spelling: parenthesi* in docs ==="
rg -n "parenthesi" README.md docs/users-guide.md docs/developers-guide.md 2>/dev/null || echo "(no matches)"

echo ""
echo "=== src/wrap/tests/inline_wrapping.rs — top 25 lines ==="
head -25 src/wrap/tests/inline_wrapping.rs

echo ""
echo "=== src/wrap/tests/token_grouping.rs — top 25 lines ==="
head -25 src/wrap/tests/token_grouping.rs

echo ""
echo "=== fence_tracker.rs docstring reference ==="
fd "fence_tracker.rs" --exec head -20 {}

Length of output: 3441



@leynos: All four checks are now resolved.

Check Status
Module-Level Documentation (❌ Error) ✅ Resolved — both inline_wrapping.rs and token_grouping.rs now carry multi-line docstrings meeting the codebase standard
User-Facing Documentation (⚠️ Warning) ✅ Resolved — zero "parenthesised" instances remain in README.md, docs/users-guide.md, or docs/developers-guide.md
Developer Documentation (⚠️ Warning) ✅ Resolved — same spelling fix confirmed in docs/developers-guide.md
Architectural Complexity & Maintainability (⚠️ Warning) ✅ Resolved — all three doc files consistently use "parenthesized"

@leynos

leynos commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

Extend the span grouping property input space with parenthesized inline
citation patterns. Add a dedicated invariant proving single and adjacent
citation chains stay inside one grouped span after tokenisation.

@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: 3

🤖 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/tests/span_grouping_props.rs`:
- Around line 44-53: Add a doc comment to the grouped_spans function explaining
that it reconstructs the original text by partitioning the token slice into
consecutive spans using determine_token_span, and that it returns a Vec<String>
where each entry is the joined tokens for a span; document its inputs (tokens:
&[String]) and that it advances by the end index returned from
determine_token_span to clarify its role in validating span boundaries.
- Around line 30-42: Add a short doc comment above the inline_citation_strategy
function that explains what citation forms this Strategy produces (single
pattern citation, multiple concatenated citations, and a reference-style
citation), why those specific examples were chosen (to exercise pattern(),
multiple back-to-back citations, and reference() parsing), and how this differs
from inline_text_strategy (which generates general inline text rather than
citation-specific formats); reference the function name inline_citation_strategy
and mention inline_text_strategy so maintainers can see the intended
distinction.

In `@tests/wrap/cli.rs`:
- Around line 137-148: Add a doc comment above the helper function
citation_link_starts explaining that it extracts link-start markers (substring
patterns like "[... ](") from the provided expected_citation string, describe
the function signature (takes &str expected_citation and returns Vec<String>),
and state why: tests use these derived markers for assertions to match dynamic
citation text instead of relying on hard-coded patterns, which prevents false
negatives when citation content or ordering changes.
🪄 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: 6ba48290-850c-4e54-9124-77cfef635955

📥 Commits

Reviewing files that changed from the base of the PR and between 430ce10 and 85e90b1.

📒 Files selected for processing (2)
  • src/wrap/tests/span_grouping_props.rs
  • tests/wrap/cli.rs

Comment thread src/wrap/tests/span_grouping_props.rs
Comment thread src/wrap/tests/span_grouping_props.rs
Comment thread tests/wrap/cli.rs
@leynos

leynos commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@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.

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Testing (Property / Proof) ⚠️ Warning The span-grouping property test strategy lacks inline citation patterns like '(n)', limiting coverage of the new try_couple_inline_link_after_opener coupling logic over the input space. Extend span_grouping_props.rs strategy with fixed citation patterns and property test the new coupling invariant alongside existing code/link span guarantees.

@leynos

leynos commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== src/wrap/tests/span_grouping_props.rs ==="
cat src/wrap/tests/span_grouping_props.rs

echo ""
echo "=== Check for inline_citation_strategy ==="
rg -n "inline_citation" src/wrap/tests/span_grouping_props.rs

echo ""
echo "=== Check for try_couple_inline_link ==="
rg -n "try_couple_inline_link\|try_couple\|citation" src/wrap/tests/span_grouping_props.rs

Length of output: 4245



@leynos: Yes — the check is resolved. src/wrap/tests/span_grouping_props.rs now contains:

  • inline_citation_strategy() — generates three distinct citation inputs, including a single pattern([1](url)) and an adjacent-citation chain.
  • Citation patterns in inline_text_strategy() — both single and adjacent citation fixtures are covered by the general partitioning property as well.
  • grouped_spans() helper — drives consecutive determine_token_span calls to build span strings.
  • determine_token_span_keeps_inline_citation_chain_atomic — property test asserting the full citation chain appears within at least one span, covering try_couple_inline_link_after_opener across the randomised input space.

No further work is required.

Add focused doc comments for the citation property strategy, grouped span
reconstruction helper, and CLI citation marker helper. These comments explain
why the helpers exist and how they support citation coupling assertions.
@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

Extend the wrap architecture notes with the tokeniser-level `([` boundary
that keeps citation openers separate from preceding punctuation. Record
`starts_inline_citation` in the key functions table so the shared predicate is
visible to maintainers.
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== tests/wrap/cli.rs — citation_link_starts doc comment ==="
grep -n -B2 -A8 "fn citation_link_starts" tests/wrap/cli.rs

echo ""
echo "=== src/wrap/tests/span_grouping_props.rs — inline_citation_strategy + grouped_spans doc comments ==="
grep -n -B2 -A10 "fn inline_citation_strategy\|fn grouped_spans" src/wrap/tests/span_grouping_props.rs

echo ""
echo "=== src/wrap/tests/inline_wrapping.rs — property tests for line-level invariants ==="
grep -n "proptest\|prop_assert\|#\[proptest\]\|wrap_width\|citation_count\|prefix_len\|generate\|strategy" src/wrap/tests/inline_wrapping.rs | head -40

echo ""
echo "=== docs/developers-guide.md — scan_trailing_punctuation + starts_inline_citation ==="
grep -n "scan_trailing_punctuation\|starts_inline_citation\|scan_plain_text_end" docs/developers-guide.md

echo ""
echo "=== docs/developers-guide.md — key types table (last 50 lines around table) ==="
grep -n "starts_inline_citation\|segment_inline\|Key types\|try_couple" docs/developers-guide.md | head -20

echo ""
echo "=== Full developers-guide.md lines 200-270 ==="
sed -n '200,270p' docs/developers-guide.md

echo ""
echo "=== Full developers-guide.md lines 290-340 ==="
sed -n '290,340p' docs/developers-guide.md

Length of output: 19761



@leynos: All seven checks are now resolved.

Check Status
Testing (Overall) — doc comments on citation_link_starts, inline_citation_strategy, grouped_spans ✅ Resolved
Unit Architecture — doc comments on test helpers ✅ Resolved
Rust Compiler Lint Integrity — doc comments on test helpers ✅ Resolved
Testing (Unit & Behavioural) — doc comments on test helpers ✅ Resolved
Architectural Complexity & Maintainability — doc comments on test helpers ✅ Resolved
Developer Documentation — segment_inline tokenisation documented ✅ Resolved — scan_trailing_punctuation_end, scan_plain_text_end, and starts_inline_citation are covered in the architecture prose (lines 231–235) and starts_inline_citation has a row in the key types table
Testing (Property / Proof) — line-level invariants property-tested ✅ Resolved — wrap_preserving_code_keeps_generated_inline_citations_attached parametrises wrap_width (24–96), prefix_len (0–32), and citation_count (1–6) and asserts all three line-level invariants

No further work is required on this PR.


🧠 Learnings used
Learnt from: CR
Repo: leynos/rstest-bdd PR: 0
File: docs/roadmap.md:0-0
Timestamp: 2026-06-07T14:05:39.362Z
Learning: Applies to docs/crates/rstest-bdd-macros/**/*.rs : Detect non-canonical harness paths and missing or ambiguous attribute-policy annotations with actionable guidance such as adding `attributes = ...` explicitly or using the canonical path

@leynos
leynos merged commit 6e002c3 into main Jun 7, 2026
3 checks passed
@leynos
leynos deleted the issue-325-keep-inline-citation-links-attached-while-wrapping branch June 7, 2026 14:14
@leynos

leynos commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai please create a new GitHub issue for the following:

Feature Request: Preserve Inline GFM Footnote Reference Attachment

mdtablefix should preserve the semantic attachment between punctuation and immediately following inline GFM footnote references during Markdown wrapping and reflow.

Problem

When formatting prose, mdtablefix may currently insert a space, newline, or newline plus indentation between sentence punctuation and an inline GFM footnote reference.

For example, this input:

The executable is then deleted.[^2]

may be reformatted as:

The executable is then deleted.
[^2]

or:

The executable is then deleted.
  [^2]

This changes the visual and semantic relationship between the reference and the sentence it annotates. Inline footnote references should remain attached to the punctuation they follow.

Desired Behaviour

When an inline GFM footnote reference immediately follows punctuation, mdtablefix should treat the punctuation plus reference as an unbreakable inline unit.

These should remain attached:

Sentence.[^1]
Sentence,[^1]
Sentence;[^1]
Sentence:[^1]
Sentence?[^1]
Sentence![^1]
Sentence)[^1]
Sentence"[^1]

If wrapping is required, the formatter should move the combined punctuation/reference unit together, rather than splitting between them.

For example:

This is a long sentence that eventually ends with a citation.[^7]

may wrap before citation.[^7], but must not become:

This is a long sentence that eventually ends with a citation.
[^7]

or:

This is a long sentence that eventually ends with a citation.
 [^7]

Important Exclusion

Footnote definitions must not be treated as inline references.

These must remain valid definition lines and must not be joined to prior punctuation:

[^7]: This is a footnote definition.

The rule should only apply to inline references of the form:

[^label]

and not to definition starts of the form:

[^label]:

Suggested Rule

During inline tokenisation or wrapping, when the formatter sees:

<punctuation><optional whitespace/newline introduced by wrapping?>[^label]

and [^label] is not followed by :, it should normalise this to:

<punctuation>[^label]

The formatter should prefer preserving existing attachment before line fitting, so wrapping logic sees the punctuation/reference pair as atomic.

Acceptance Criteria

  • mdtablefix --wrap does not insert whitespace between punctuation and immediately following inline GFM footnote references.
  • Existing input like word.[^1] remains word.[^1].
  • Previously split output like word.\n[^1] is normalised back to word.[^1] when it is clearly part of the same paragraph.
  • Footnote definition lines like [^1]: note text are not altered or joined to previous prose.
  • Markdownlint remains clean after formatting.
  • Behaviour is covered by tests for same-line spacing, newline splitting, newline plus indentation, and footnote definition exclusion.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo leynos/mdtablefix \
  --title "Feature Request: Preserve Inline GFM Footnote Reference Attachment" \
  --assignee leynos \
  --body "## Feature Request: Preserve Inline GFM Footnote Reference Attachment

\`mdtablefix\` should preserve the semantic attachment between punctuation and immediately following inline GFM footnote references during Markdown wrapping and reflow.

### Problem

When formatting prose, \`mdtablefix\` may currently insert a space, newline, or newline plus indentation between sentence punctuation and an inline GFM footnote reference.

For example, this input:

\`\`\`markdown
The executable is then deleted.[^2]
\`\`\`

may be reformatted as:

\`\`\`markdown
The executable is then deleted.
[^2]
\`\`\`

or:

\`\`\`markdown
The executable is then deleted.
  [^2]
\`\`\`

This changes the visual and semantic relationship between the reference and the sentence it annotates. Inline footnote references should remain attached to the punctuation they follow.

### Desired Behaviour

When an inline GFM footnote reference immediately follows punctuation, \`mdtablefix\` should treat the punctuation plus reference as an unbreakable inline unit.

These should remain attached:

\`\`\`markdown
Sentence.[^1]
Sentence,[^1]
Sentence;[^1]
Sentence:[^1]
Sentence?[^1]
Sentence![^1]
Sentence)[^1]
Sentence\"[^1]
\`\`\`

If wrapping is required, the formatter should move the combined punctuation/reference unit together, rather than splitting between them.

For example:

\`\`\`markdown
This is a long sentence that eventually ends with a citation.[^7]
\`\`\`

may wrap before \`citation.[^7]\`, but must not become:

\`\`\`markdown
This is a long sentence that eventually ends with a citation.
[^7]
\`\`\`

or:

\`\`\`markdown
This is a long sentence that eventually ends with a citation.
 [^7]
\`\`\`

### Important Exclusion

Footnote definitions must not be treated as inline references.

These must remain valid definition lines and must not be joined to prior punctuation:

\`\`\`markdown
[^7]: This is a footnote definition.
\`\`\`

The rule should only apply to inline references of the form:

\`\`\`markdown
[^label]
\`\`\`

and not to definition starts of the form:

\`\`\`markdown
[^label]:
\`\`\`

### Suggested Rule

During inline tokenisation or wrapping, when the formatter sees:

\`\`\`
<punctuation><optional whitespace/newline introduced by wrapping?>[^label]
\`\`\`

and \`[^label]\` is not followed by \`:\`, it should normalise this to:

\`\`\`
<punctuation>[^label]
\`\`\`

The formatter should prefer preserving existing attachment before line fitting, so wrapping logic sees the punctuation/reference pair as atomic.

### Acceptance Criteria

- \`mdtablefix --wrap\` does not insert whitespace between punctuation and immediately following inline GFM footnote references.
- Existing input like \`word.[^1]\` remains \`word.[^1]\`.
- Previously split output like \`word.\n[^1]\` is normalised back to \`word.[^1]\` when it is clearly part of the same paragraph.
- Footnote definition lines like \`[^1]: note text\` are not altered or joined to previous prose.
- Markdownlint remains clean after formatting.
- Behaviour is covered by tests for same-line spacing, newline splitting, newline plus indentation, and footnote definition exclusion.

---

Requested by `@leynos` via PR `#348` (https://github.com/leynos/mdtablefix/pull/348)."

Length of output: 3499



@leynos: Issue created — #353, assigned to you with the full specification and a backlink to this PR.

@leynos

leynos commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai please create a new GitHub issue for the following:

Here is a minimum reproducible case for mdtablefix 0.4.0.

cat > repro.md <<'EOF'
- Message: `Ensure the manifest exists or pass \`--file\` with the correct path.` The docs should pin that wording.
EOF

mdtablefix --wrap --renumber --breaks --ellipsis --fences --in-place repro.md
cat -n repro.md

Actual output:

     1	- Message: `Ensure the manifest exists or pass \`--file\` with the correct path.
     2	  ` The docs should pin that wording.

Expected output:

- Message: `Ensure the manifest exists or pass \`--file\` with the correct path.`
  The docs should pin that wording.

Or, if the formatter normalizes nested backticks, this would also be acceptable:

- Message: ``Ensure the manifest exists or pass `--file` with the correct path.``
  The docs should pin that wording.

Bug summary:

mdtablefix --wrap wraps an inline code span containing escaped backticks by moving the closing delimiter onto the next line. This changes the Markdown structure and can also create markdownlint line-length failures in longer real-world paragraphs.

Tool version:

mdtablefix 0.4.0

Workaround:

Use doubled-backtick code spans before formatting:

- Message: ``Ensure the manifest exists or pass `--file` with the correct path.``
  The docs should pin that wording.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo leynos/mdtablefix \
  --title "Bug: --wrap splits inline code span containing escaped backticks across lines" \
  --assignee leynos \
  --body "## Bug Report

### Version

\`mdtablefix 0.4.0\`

### Minimum Reproducible Case

\`\`\`sh
cat > repro.md <<'EOF'
- Message: \`Ensure the manifest exists or pass \\\`--file\\\` with the correct path.\` The docs should pin that wording.
EOF

mdtablefix --wrap --renumber --breaks --ellipsis --fences --in-place repro.md
cat -n repro.md
\`\`\`

### Actual Output

\`\`\`markdown
     1	- Message: \`Ensure the manifest exists or pass \\\`--file\\\` with the correct path.
     2	  \` The docs should pin that wording.
\`\`\`

### Expected Output

\`\`\`markdown
- Message: \`Ensure the manifest exists or pass \\\`--file\\\` with the correct path.\`
  The docs should pin that wording.
\`\`\`

Or, if the formatter normalizes nested backticks, this would also be acceptable:

\`\`\`markdown
- Message: \`\`Ensure the manifest exists or pass \`--file\` with the correct path.\`\`
  The docs should pin that wording.
\`\`\`

### Description

\`mdtablefix --wrap\` wraps an inline code span containing escaped backticks by moving the closing backtick delimiter onto the next line. This changes the Markdown structure — the output is no longer a valid inline code span — and can also create markdownlint line-length failures in longer real-world paragraphs.

The tokeniser or wrapping logic appears to misidentify the escaped backtick (\`\\\`\`) inside the code span as the closing delimiter, causing the real closing delimiter to be treated as a new token that may fall onto the following line after reflow.

### Workaround

Use doubled-backtick code spans before formatting:

\`\`\`markdown
- Message: \`\`Ensure the manifest exists or pass \`--file\` with the correct path.\`\`
  The docs should pin that wording.
\`\`\`

### Acceptance Criteria

- \`mdtablefix --wrap\` does not move the closing delimiter of an inline code span containing escaped backticks onto the next line.
- Input like \`word.\\\`inner\\\`.rest\` is tokenised and wrapped as a single atomic code span.
- The fix does not regress existing code-span wrapping behaviour.
- Behaviour is covered by tests for inline code spans with escaped backticks in both list-item and paragraph contexts.

---

Reported by \`@leynos\` via PR `#348` (https://github.com/leynos/mdtablefix/pull/348)."

Length of output: 2523


🏁 Script executed:

#!/bin/bash
gh issue list --repo leynos/mdtablefix --author leynos --state open --limit 5 --json number,title,url | jq '.[] | select(.title | contains("escaped backtick")) | .url'

Length of output: 422



@leynos: Issue created — #354, assigned to you with the full MRE, description, and acceptance criteria.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Keep inline citation links attached while wrapping

1 participant