feat: add line highlighting support to code blocks#3193
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@gatsby-config.ts`:
- Around line 12-18: Replace the untyped any usage in remarkCodeMeta by
importing and using mdast types: add import { Root, Code } from 'mdast' (and
install `@types/mdast` if not present), change the signature to const
remarkCodeMeta = () => (tree: Root) => { visit(tree, 'code', (node: Code) => {
... }) }, and adjust assignments to node.data/node.data.hProperties with proper
typing (e.g., node.data = node.data || {} as Record<string, any>) so node.meta
is typed as string | undefined and node.data accesses are type-safe while
keeping the rest of the logic in the visit callback intact.
In `@package.json`:
- Line 43: package.json currently pins the dependency "@ably/ui" to a dev
snapshot "17.14.0-dev.2fa7246503"; replace this with a stable released version
(e.g., "17.14.0" or the latest stable semver) or explicitly document/justify the
snapshot if it must remain. Update the "@ably/ui" entry in package.json to the
chosen stable version string, run npm/yarn install to update lockfile, and
ensure package-lock.json/yarn.lock is committed; if the snapshot is intentional,
add a comment in the project docs explaining why that exact dev snapshot is
required.
In `@src/components/Markdown/CodeBlock.test.tsx`:
- Around line 5-8: The mocked Icon component in the jest.mock call returns a
traditional function; change it to an arrow function to match project style:
update the factory to return const MockIcon = () => <span
data-testid="mock-icon" /> (or inline () => <span data-testid="mock-icon" />) so
the mock for '@ably/ui/core/Icon' uses an arrow component named MockIcon.
- Around line 11-52: Replace the loose any types used in tests: change the
ButtonWithTooltip mock prop signature to a proper React props type (e.g.,
PropsWithChildren or an explicit {children: React.ReactNode} shape) for the
ButtonWithTooltip mock, type the mock function parameter spreads for
mockHighlightSnippet, mockParseLineHighlights, and mockSplitHtmlLines with
appropriate parameter/return types matching the real
highlightSnippet/parseLineHighlights/splitHtmlLines signatures (e.g., (code:
string, lang?: string) => string or the actual types from
`@ably/ui/core/utils/syntax-highlighter`), and replace the codeProps:
Record<string, any> in buildCodeChild with a precise object type (e.g., {
className: string; children: string; 'data-meta'?: string }) so all mocks and
helpers (ButtonWithTooltip, mockHighlightSnippet, mockParseLineHighlights,
mockSplitHtmlLines, buildCodeChild) use explicit TypeScript types instead of
any.
In `@src/pages/docs/ai-transport/token-streaming/message-per-response.mdx`:
- Around line 64-77: Remove the temporary "Line highlighting demo:" code block
(the ```javascript highlight="+1-2,3,-5-6,7-8" ... ``` fenced block and its
preceding heading) from
src/pages/docs/ai-transport/token-streaming/message-per-response.mdx so the
preview-only demo does not ship in published docs; simply delete that entire
demo section including the "Line highlighting demo:" line and the wrapped Code
block.
src/pages/docs/ai-transport/token-streaming/message-per-response.mdx
Outdated
Show resolved
Hide resolved
8304698 to
70f5a5b
Compare
70f5a5b to
7e31955
Compare
7e31955 to
9211d9e
Compare
| }} | ||
| /> | ||
| {hasHighlights ? ( | ||
| splitHtmlLines(sanitize(highlightedContent ?? '')).map((lineHtml, i) => { |
There was a problem hiding this comment.
Some duplication from AblyUI as we don't use their Code component directly as we add copy and DOMPurify.
package.json
Outdated
| }, | ||
| "dependencies": { | ||
| "@ably/ui": "17.13.2", | ||
| "@ably/ui": "17.14.0-dev.dff5348de8", |
There was a problem hiding this comment.
will be changed to published version once package changes are merged and published.
9211d9e to
e934785
Compare
e934785 to
7ccc117
Compare
package.json
Outdated
| }, | ||
| "dependencies": { | ||
| "@ably/ui": "17.13.2", | ||
| "@ably/ui": "v17.14.0-dev.fc30134778", |
There was a problem hiding this comment.
dev package will be dropped before merge
b2cfed0 to
86c36a4
Compare
|
Thanks for this Matt! I'm still questioning the value of having 3 different highlight colours if I'm honest. If we do think it's necessary then I think deletion should be red, rather than yellow, and we need to make the generic highlight more obvious than the blue - especially given that I think this will be by far the most used. Might be worth getting a view from design if you haven't already. I also think we should strip the highlight variable out when we compile the raw markdown to keep it simple. |
86c36a4 to
36f7496
Compare
@m-hulbert Design are back next week, so will have their feedback then. In the mean time, I've changed the colours to be more noticeable(100 -> 200). Vercel docs inspired the blue choice initially.
Done, can be seen on the review app |
36f7496 to
c833e08
Compare
c833e08 to
d6ac9c9
Compare
aralovelace
left a comment
There was a problem hiding this comment.
Looks good @matt423 just nitpicks on my end
| children, | ||
| language = 'javascript', | ||
| }) => { | ||
| const meta: string | undefined = (children as React.ReactElement)?.props?.['data-meta']; |
There was a problem hiding this comment.
[nitpick] should we add more typeguard like?
const meta: string | undefined =
React.isValidElement(children) && typeof children.props?.['data-meta'] === 'string'
? children.props['data-meta']
: undefined;
| const highlightType = highlights[lineNum]; | ||
| const highlightClass = highlightType ? LINE_HIGHLIGHT_CLASSES[highlightType] : undefined; | ||
| return ( | ||
| <span key={i} className={highlightClass} style={{ display: 'flex', minWidth: '100%' }}> |
There was a problem hiding this comment.
do we need aria labels here?
There was a problem hiding this comment.
Had Claude do some research and it noted that most other platform providers(Vercel, Github etc) don't use aria labels for the highlights
aralovelace
left a comment
There was a problem hiding this comment.
Also ask Coderabbit CLI and suggested:
🚀 Quick Wins to Implement Now:
⏺ Read 1 file (ctrl+o to expand)
⏺ Here's a specific code improvement you can implement immediately:
// CodeBlock.tsx - Add this after line 39:
const sanitizedLines = useMemo(() => {
if (!hasHighlights || !highlightedContent) return null;
return splitHtmlLines(sanitize(highlightedContent));
}, [hasHighlights, highlightedContent]);
// Then update line 55-64 to use the memoized value:
{hasHighlights && sanitizedLines ? (
<code className="ui-text-code" style={{ whiteSpace: 'pre-wrap' }}>
{sanitizedLines.map((lineHtml, i) => {
const lineNum = i + 1;
const highlightType = highlights[lineNum];
const highlightClass = highlightType ? LINE_HIGHLIGHT_CLASSES[highlightType] : undefined;
return (
<span key={i} className={highlightClass} style={{ display: 'flex', minWidth: '100%' }}>
<span style={{ flex: 1 }} dangerouslySetInnerHTML={{ __html: lineHtml || ' ' }} />
</span>
);
})}
</code>
This change will prevent re-sanitizing and re-splitting HTML on every render, which is especially important for pages with many code blocks.
what you think?
@aralovelace The inputs to sanitize/splitHtmlLines are already derived from highlightedContent which is memoized — and since these are static doc pages the code block props never change after mount, so there's no real re-render scenario where this saves us anything. Happy to add it if you feel strongly but I think it's unnecessary complexity for zero practical gain here. |
d6ac9c9 to
2b288a4
Compare
Add highlight="..." syntax to MDX code fences to visually highlight lines as additions, removals, or neutral highlights. Uses a remark plugin to preserve code fence meta strings as data-meta attributes through the MDX pipeline.
Strip code fence meta strings (e.g. highlight="2,8") during the MDX-to-markdown transpilation so they don't leak into the clean markdown served to LLMs.
CLAUDE.md and CONTRIBUTING.md
2b288a4 to
18f4b2f
Compare
Description
Add
highlight="..."syntax to MDX code fences to visually highlight lines as additions (green), removals (orange), or neutral highlights (blue).Review App
Example usage:
Syntax supports individual lines and ranges:
3highlights line 3 (blue)+3highlights line 3 as an addition (green)-3highlights line 3 as a removal (orange)1-6highlights lines 1 through 6 (blue)+1-6highlights lines 1 through 6 as additions (green)-1-6highlights lines 1 through 6 as removals (orange)highlight="+1-2,3,-5-6,7-8"Changes:
remarkCodeMetaremark plugin ingatsby-config.tsto preserve code fence meta strings asdata-metaattributes through the MDX pipelineCodeBlock.tsxto parse highlights and render per-line with highlight classesCodeBlock.test.tsxwith tests for highlight renderingsetupTests.jsfor new exports from@ably/ui@ably/uito17.14.0-dev.2fa7246503(will update to final version before merge)Companion PR: https://github.com/ably/website/pull/7813
Checklist