discover: support hyperlinks and newlines in protocol descriptions - #2985
Conversation
Adds a LinkifiedText component that turns markdown-style [text](https://...) links and bare https:// URLs in a protocol's description into clickable links, restricted to https to match the existing isSafeHttpsUrl convention used for protocol.websiteUrl. Also preserves embedded newlines via white-space: pre-line, which the description text previously collapsed.
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-90c10b53fa1264106624 |
There was a problem hiding this comment.
Pull request overview
Adds safe HTTPS linkification and multiline formatting to Discover protocol descriptions.
Changes:
- Adds and tests a lightweight link parser and renderer.
- Integrates linkified descriptions into protocol details.
- Documents supported description syntax.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
ProtocolDetailsPanel/styles.scss |
Preserves newlines and styles links. |
ProtocolDetailsPanel/index.tsx |
Renders descriptions with LinkifiedText. |
LinkifiedText/parseLinkedText.ts |
Parses Markdown and bare HTTPS links. |
LinkifiedText/index.tsx |
Renders parsed links safely. |
LinkifiedText/__tests__/parseLinkedText.test.ts |
Tests parser behavior. |
@shared/api/types/types.ts |
Documents description syntax. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Addresses Copilot review feedback on #2985: - parseLinkedText previously excluded all parentheses from a URL's character class, truncating URLs that legitimately contain them (e.g. Wikipedia-style https://.../Function_(mathematics)), and for the markdown form the first ")" inside the URL was mistaken for the link's closing paren. Replaced the regex-based matcher with a small scanner that tracks paren depth so balanced parens stay part of the URL, and only an unmatched ")" ends it. Added regression tests for both the bare-URL and markdown-link forms. - ProtocolDetailsPanel's global stylesheet resets all <a> underlines, so the description's links were distinguished from surrounding text by color alone. Restored text-decoration: underline on them so the link cue doesn't depend on color perception.
Addresses further review feedback on #2985: - Copilot: a markdown label containing its own https:// URL (e.g. [https://a.example](https://b.example)) was mishandled because the parser scanned for bare https:// occurrences first, matching the label's URL before considering the markdown link around it. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label, since the backward-scanning opener regex always matched from the leftmost "[" rather than the nearest one. Replaced the backward-scanning approach with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found starting there; an incomplete/invalid "[" is left as literal text and scanning resumes just past it. This also incidentally required tightening the URL boundary characters to include quotes, since a bare URL inside quotes was absorbing the closing quote. Added regression tests for all three cases.
Addresses review feedback on #994: - Copilot: __tests__/helpers/linkedText.test.ts imported "helpers/linkedText", which jest.config.js's moduleNameMapper remaps to __mocks__/helpers/linkedText - a file that doesn't exist, so the suite failed to resolve the module before any test ran. Import the real module via a relative path instead. - Copilot: LinkedText had no component-level test exercising the actual tappable behavior (pressing a parsed segment opens the right URL) or its accessibility semantics. - Copilot: bare URLs wrapped in quotes (e.g. "https://example.com/x") absorbed the closing quote into the tappable URL. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label (matching the same fix applied on the extension PR, stellar/freighter#2985). Replaced the backward-scanning markdown-opener regex with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found; this also fixes a related Copilot finding on the extension PR where a label containing its own https:// URL was mishandled. Tightened the URL boundary characters to also stop at quotes. Added a LinkedText component test (renders both link forms, presses each, asserts the in-app browser is opened with the correct URL) and accessibilityRole="link" on tappable segments, matching the existing pattern in BalancesList.tsx.
Addresses further Copilot feedback on #2985: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as clickable links, even though the existing isSafeHttpsUrl guard in Discover/index.tsx would reject them. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; reject candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. Added regression tests for both.
Addresses further Copilot feedback on #994: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as tappable links. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; rejected candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. - Added the same eslint-disable-next-line react/no-array-index-key suppression to the linked-segment branch in LinkedText.tsx that the plain-text branch already had, since the AirBnB rule also flags indexes used inside template literals. Added regression tests for both parser fixes, matching stellar/freighter#2985.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
extension/src/popup/basics/LinkifiedText/index.tsx:25
- An empty or whitespace-only markdown label (for example,
[](https://example.com)) is accepted, so this renders a focusable external link with no accessible name or visible target. Fall back to the URL when the supplied label has no meaningful text.
{segment.text}
extension/src/popup/basics/LinkifiedText/parseLinkedText.ts:158
- Failed markdown candidates are retried one character later, while
tryParseMarkdownLinkrescans the remaining string withindexOfandincludes. A malformed API description such as many[characters followed by]therefore takes quadratic time; there is no response-length validation before this runs in the popup. Parse brackets in one forward pass or enforce a strict description-length limit to prevent a malformed response from freezing the UI.
const link = tryParseMarkdownLink(text, bracketIndex);
* discovery: support hyperlinks in protocol descriptions Adds a LinkedText component that turns markdown-style [text](https://...) links and bare https:// URLs in a protocol's description into tappable links via the existing Text `url` prop, restricted to https to match the HTTPS-only convention already used for protocol URLs elsewhere (helpers/protocols.ts). Newlines already render correctly in React Native, so no change was needed there. * discovery: fix link parsing for balanced parentheses in URLs Ports the fix from stellar/freighter PR #2985 (Copilot review feedback): the regex-based matcher excluded all parentheses from a URL's character class, truncating URLs that legitimately contain them (e.g. Wikipedia-style https://.../Function_(mathematics)), and for the markdown form the first ")" inside the URL was mistaken for the link's closing paren. Replaced it with a small scanner that tracks paren depth so balanced parens stay part of the URL, and only an unmatched ")" ends it. Added regression tests for both the bare-URL and markdown-link forms. * discovery: fix link parsing bugs and address review feedback Addresses review feedback on #994: - Copilot: __tests__/helpers/linkedText.test.ts imported "helpers/linkedText", which jest.config.js's moduleNameMapper remaps to __mocks__/helpers/linkedText - a file that doesn't exist, so the suite failed to resolve the module before any test ran. Import the real module via a relative path instead. - Copilot: LinkedText had no component-level test exercising the actual tappable behavior (pressing a parsed segment opens the right URL) or its accessibility semantics. - Copilot: bare URLs wrapped in quotes (e.g. "https://example.com/x") absorbed the closing quote into the tappable URL. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label (matching the same fix applied on the extension PR, stellar/freighter#2985). Replaced the backward-scanning markdown-opener regex with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found; this also fixes a related Copilot finding on the extension PR where a label containing its own https:// URL was mishandled. Tightened the URL boundary characters to also stop at quotes. Added a LinkedText component test (renders both link forms, presses each, asserts the in-app browser is opened with the correct URL) and accessibilityRole="link" on tappable segments, matching the existing pattern in BalancesList.tsx. * discovery: reject malformed URLs, keep brackets in markdown destinations Addresses further Copilot feedback on #994: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as tappable links. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; rejected candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. - Added the same eslint-disable-next-line react/no-array-index-key suppression to the linked-segment branch in LinkedText.tsx that the plain-text branch already had, since the AirBnB rule also flags indexes used inside template literals. Added regression tests for both parser fixes, matching stellar/freighter#2985. --------- Co-authored-by: Claude <noreply@anthropic.com>
…2985) * discover: support hyperlinks and newlines in protocol descriptions Adds a LinkifiedText component that turns markdown-style [text](https://...) links and bare https:// URLs in a protocol's description into clickable links, restricted to https to match the existing isSafeHttpsUrl convention used for protocol.websiteUrl. Also preserves embedded newlines via white-space: pre-line, which the description text previously collapsed. * discover: fix link parsing for balanced parens, restore link underline Addresses Copilot review feedback on #2985: - parseLinkedText previously excluded all parentheses from a URL's character class, truncating URLs that legitimately contain them (e.g. Wikipedia-style https://.../Function_(mathematics)), and for the markdown form the first ")" inside the URL was mistaken for the link's closing paren. Replaced the regex-based matcher with a small scanner that tracks paren depth so balanced parens stay part of the URL, and only an unmatched ")" ends it. Added regression tests for both the bare-URL and markdown-link forms. - ProtocolDetailsPanel's global stylesheet resets all <a> underlines, so the description's links were distinguished from surrounding text by color alone. Restored text-decoration: underline on them so the link cue doesn't depend on color perception. * discover: fix nested-bracket and label-URL parsing bugs Addresses further review feedback on #2985: - Copilot: a markdown label containing its own https:// URL (e.g. [https://a.example](https://b.example)) was mishandled because the parser scanned for bare https:// occurrences first, matching the label's URL before considering the markdown link around it. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label, since the backward-scanning opener regex always matched from the leftmost "[" rather than the nearest one. Replaced the backward-scanning approach with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found starting there; an incomplete/invalid "[" is left as literal text and scanning resumes just past it. This also incidentally required tightening the URL boundary characters to include quotes, since a bare URL inside quotes was absorbing the closing quote. Added regression tests for all three cases. * discover: reject malformed URLs, keep brackets in markdown destinations Addresses further Copilot feedback on #2985: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as clickable links, even though the existing isSafeHttpsUrl guard in Discover/index.tsx would reject them. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; reject candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. Added regression tests for both. --------- Co-authored-by: Claude <noreply@anthropic.com>
* discovery: support hyperlinks in protocol descriptions Adds a LinkedText component that turns markdown-style [text](https://...) links and bare https:// URLs in a protocol's description into tappable links via the existing Text `url` prop, restricted to https to match the HTTPS-only convention already used for protocol URLs elsewhere (helpers/protocols.ts). Newlines already render correctly in React Native, so no change was needed there. * discovery: fix link parsing for balanced parentheses in URLs Ports the fix from stellar/freighter PR #2985 (Copilot review feedback): the regex-based matcher excluded all parentheses from a URL's character class, truncating URLs that legitimately contain them (e.g. Wikipedia-style https://.../Function_(mathematics)), and for the markdown form the first ")" inside the URL was mistaken for the link's closing paren. Replaced it with a small scanner that tracks paren depth so balanced parens stay part of the URL, and only an unmatched ")" ends it. Added regression tests for both the bare-URL and markdown-link forms. * discovery: fix link parsing bugs and address review feedback Addresses review feedback on #994: - Copilot: __tests__/helpers/linkedText.test.ts imported "helpers/linkedText", which jest.config.js's moduleNameMapper remaps to __mocks__/helpers/linkedText - a file that doesn't exist, so the suite failed to resolve the module before any test ran. Import the real module via a relative path instead. - Copilot: LinkedText had no component-level test exercising the actual tappable behavior (pressing a parsed segment opens the right URL) or its accessibility semantics. - Copilot: bare URLs wrapped in quotes (e.g. "https://example.com/x") absorbed the closing quote into the tappable URL. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label (matching the same fix applied on the extension PR, stellar/freighter#2985). Replaced the backward-scanning markdown-opener regex with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found; this also fixes a related Copilot finding on the extension PR where a label containing its own https:// URL was mishandled. Tightened the URL boundary characters to also stop at quotes. Added a LinkedText component test (renders both link forms, presses each, asserts the in-app browser is opened with the correct URL) and accessibilityRole="link" on tappable segments, matching the existing pattern in BalancesList.tsx. * discovery: reject malformed URLs, keep brackets in markdown destinations Addresses further Copilot feedback on #994: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as tappable links. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; rejected candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. - Added the same eslint-disable-next-line react/no-array-index-key suppression to the linked-segment branch in LinkedText.tsx that the plain-text branch already had, since the AirBnB rule also flags indexes used inside template literals. Added regression tests for both parser fixes, matching stellar/freighter#2985. --------- Co-authored-by: Claude <noreply@anthropic.com>
* discovery: support hyperlinks in protocol descriptions Adds a LinkedText component that turns markdown-style [text](https://...) links and bare https:// URLs in a protocol's description into tappable links via the existing Text `url` prop, restricted to https to match the HTTPS-only convention already used for protocol URLs elsewhere (helpers/protocols.ts). Newlines already render correctly in React Native, so no change was needed there. * discovery: fix link parsing for balanced parentheses in URLs Ports the fix from stellar/freighter PR #2985 (Copilot review feedback): the regex-based matcher excluded all parentheses from a URL's character class, truncating URLs that legitimately contain them (e.g. Wikipedia-style https://.../Function_(mathematics)), and for the markdown form the first ")" inside the URL was mistaken for the link's closing paren. Replaced it with a small scanner that tracks paren depth so balanced parens stay part of the URL, and only an unmatched ")" ends it. Added regression tests for both the bare-URL and markdown-link forms. * discovery: fix link parsing bugs and address review feedback Addresses review feedback on #994: - Copilot: __tests__/helpers/linkedText.test.ts imported "helpers/linkedText", which jest.config.js's moduleNameMapper remaps to __mocks__/helpers/linkedText - a file that doesn't exist, so the suite failed to resolve the module before any test ran. Import the real module via a relative path instead. - Copilot: LinkedText had no component-level test exercising the actual tappable behavior (pressing a parsed segment opens the right URL) or its accessibility semantics. - Copilot: bare URLs wrapped in quotes (e.g. "https://example.com/x") absorbed the closing quote into the tappable URL. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label (matching the same fix applied on the extension PR, stellar/freighter#2985). Replaced the backward-scanning markdown-opener regex with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found; this also fixes a related Copilot finding on the extension PR where a label containing its own https:// URL was mishandled. Tightened the URL boundary characters to also stop at quotes. Added a LinkedText component test (renders both link forms, presses each, asserts the in-app browser is opened with the correct URL) and accessibilityRole="link" on tappable segments, matching the existing pattern in BalancesList.tsx. * discovery: reject malformed URLs, keep brackets in markdown destinations Addresses further Copilot feedback on #994: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as tappable links. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; rejected candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. - Added the same eslint-disable-next-line react/no-array-index-key suppression to the linked-segment branch in LinkedText.tsx that the plain-text branch already had, since the AirBnB rule also flags indexes used inside template literals. Added regression tests for both parser fixes, matching stellar/freighter#2985. --------- Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…2985) (#2989) * discover: support hyperlinks and newlines in protocol descriptions Adds a LinkifiedText component that turns markdown-style [text](https://...) links and bare https:// URLs in a protocol's description into clickable links, restricted to https to match the existing isSafeHttpsUrl convention used for protocol.websiteUrl. Also preserves embedded newlines via white-space: pre-line, which the description text previously collapsed. * discover: fix link parsing for balanced parens, restore link underline Addresses Copilot review feedback on #2985: - parseLinkedText previously excluded all parentheses from a URL's character class, truncating URLs that legitimately contain them (e.g. Wikipedia-style https://.../Function_(mathematics)), and for the markdown form the first ")" inside the URL was mistaken for the link's closing paren. Replaced the regex-based matcher with a small scanner that tracks paren depth so balanced parens stay part of the URL, and only an unmatched ")" ends it. Added regression tests for both the bare-URL and markdown-link forms. - ProtocolDetailsPanel's global stylesheet resets all <a> underlines, so the description's links were distinguished from surrounding text by color alone. Restored text-decoration: underline on them so the link cue doesn't depend on color perception. * discover: fix nested-bracket and label-URL parsing bugs Addresses further review feedback on #2985: - Copilot: a markdown label containing its own https:// URL (e.g. [https://a.example](https://b.example)) was mishandled because the parser scanned for bare https:// occurrences first, matching the label's URL before considering the markdown link around it. - aristidesstaffieri: a stray, unmatched "[" before a real markdown link could swallow the real link into its own (invalid) label, since the backward-scanning opener regex always matched from the leftmost "[" rather than the nearest one. Replaced the backward-scanning approach with a forward scanner that looks for the next "[" or bare "https://" (whichever comes first) and only commits to a markdown link once a complete, valid [label](https://...) is found starting there; an incomplete/invalid "[" is left as literal text and scanning resumes just past it. This also incidentally required tightening the URL boundary characters to include quotes, since a bare URL inside quotes was absorbing the closing quote. Added regression tests for all three cases. * discover: reject malformed URLs, keep brackets in markdown destinations Addresses further Copilot feedback on #2985: - A candidate was only checked against the "https://" text prefix, so malformed values like bare "https://" or "https://?query" (no host) were still emitted as clickable links, even though the existing isSafeHttpsUrl guard in Discover/index.tsx would reject them. Validate every candidate with `new URL(...)` and require the "https:" protocol before treating it as a link; reject candidates are left as plain text (the original substring, not `url.href`, is still used for display/navigation, since the WHATWG URL parser normalizes some inputs like adding a trailing slash to a bare origin). - The URL boundary excluded square brackets universally, which broke markdown-link destinations containing the common `?tag[]=value` query syntax: the parse failed and the same target was then emitted as a truncated bare link pointing at the wrong address. Brackets are now only excluded from a *bare* URL's boundary (to avoid ambiguity with an immediately following markdown link) - a markdown destination's balanced closing paren is enough to bound it on its own. Added regression tests for both. --------- Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
What
Adds a
LinkifiedTextcomponent (extension/src/popup/basics/LinkifiedText/) that renders a protocol'sdescriptionwhile turning markdown-style[text](https://...)links and barehttps://URLs into clickable<a>links, and preserves embedded newlines (white-space: pre-line) inProtocolDetailsPanel.Why
The
/protocolsdescription field is currently rendered as inert plain text with newlines collapsed. We just used this field to add a security-incident notice with a link (see thestellar/kubePR for the mainnetBlendentry) and want that link to actually be clickable, and future multi-line descriptions to render correctly.Implementation notes
https://URLs are ever linkified, matching the existingisSafeHttpsUrlconvention already used forprotocol.websiteUrlinDiscover/index.tsx(nohttp:,javascript:, etc.).parseLinkedTextis a small regex-based parser and the output is only ever plain text or<a>elements (nodangerouslySetInnerHTML), so this is safe on the API-sourced, untrusteddescriptionstring.ProtocolEntry.description(@shared/api/types/types.ts) documenting the supported syntax.Known limitations
repo.yarnpkg.comwas unreachable). I verified theparseLinkedTextregex logic against the added test cases with a standalone Node script, but the actual test suite has not been run against this branch.Generated by Claude Code