CJK autolink boundary handling leaves subsequent URLs as plain text
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 302
- Avg merge
- 3d 19h
- Merged PRs (30d)
- 14
Description
Bug Description
With @streamdown/cjk enabled, multiple bare URLs separated by fullwidth semicolons (;, U+FF1B) without whitespace render with only the first URL clickable. Subsequent URLs remain plain text. This appears to be an uncovered case in #327 (fixing #326), rather than an error in the boundary index calculation.
Steps to Reproduce
Render the code sample below with mode="static", then with mode="streaming". In both cases only the first URL becomes a link. Adding a space after each semicolon, or writing three explicit Markdown links, makes all three links work.
Expected Behavior
All three URLs should be separate clickable links, with the semicolons preserved as plain text.
Actual Behavior
Only https://example.com/a is clickable. The rest is plain text. With a custom a renderer to inspect the HTML:
<p>参考资料:<a href="https://example.com/a">https://example.com/a</a>;https://example.com/b;https://example.com/c</p>
Code Sample
import { Streamdown } from 'streamdown';
import { cjk } from '@streamdown/cjk';
const markdown =
'参考资料:https://example.com/a;https://example.com/b;https://example.com/c';
export default function App() {
return (
<Streamdown mode="static" plugins={{ cjk }}>
{markdown}
</Streamdown>
);
}
Streamdown Version
2.5.0 and 2.6.0; @streamdown/cjk 1.0.3
React Version
19.2.4
Node.js Version
24.13.1
Browser(s)
No response
Operating System
None
Additional Context
The following diagram illustrates the observed parsing result; it is not a browser screenshot.
flowchart TD
input["Input: URL A;URL B;URL C"] --> gfm["GFM: one link containing A;B;C"]
gfm --> boundary["CJK boundary: split at the first ;"]
boundary --> actual["Actual: link A + plain text ;B;C"]
input -. "Expected rendering" .-> expected["link A + ; + link B + ; + link C"]
style boundary fill:#fff3cd,stroke:#b58105,color:#222
style actual fill:#ffe8e8,stroke:#c74343,color:#222
style expected fill:#e7f6ec,stroke:#348450,color:#222
In #327, remarkCjkAutolinkBoundary finds the first CJK punctuation boundary and replaces the original link with:
const trimmedLink = buildAutolink(trimmedUrl, node);
const trailingText = buildTrailingText(trailing);
parent.children.splice(index, 1, trimmedLink, trailingText);
For URL。谢谢, this correctly preserves trailing prose as text. For URL A;URL B;URL C, the trailing part contains two more URLs. It becomes a text node and is not re-linkified by this link-node visitor.
Without the boundary handling, GFM also does not produce three correct links: it treats the sequence as one long URL. This report is about preserving subsequent URLs while applying the CJK boundary fix, not claiming the pre-#327 output was correct.
Verification:
- Reproduced with Streamdown 2.5.0 and @streamdown/cjk 1.0.3.
- Also tested the original three-URL message after upgrading to Streamdown 2.6.0 with @streamdown/cjk 1.0.3, without our local workaround: the same two static/streaming regression tests fail.
- Adding a space after each semicolon makes all three links render correctly.
- Writing three explicit Markdown links also works.
- Tests used React server rendering with complete content in both modes; incremental token delivery was not tested.
Could the CJK autolink handling preserve or re-detect subsequent bare URLs after splitting at punctuation? A regression test for multiple URLs separated by fullwidth semicolons would cover this case.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the remarkCjkAutolinkBoundary entry point described in the issue and inspect the static and streaming regression tests that reproduce the fullwidth-semicolon case. Verify how the trailing text is handled after splitting the first link. Done means all three bare URLs render as separate clickable links with semicolons preserved in both modes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100