Skip to content

fix: table rendering#2829

Open
mmioana wants to merge 2 commits intodevelopfrom
fix/strapi-table-renderer
Open

fix: table rendering#2829
mmioana wants to merge 2 commits intodevelopfrom
fix/strapi-table-renderer

Conversation

@mmioana
Copy link
Copy Markdown
Contributor

@mmioana mmioana commented Apr 24, 2026

Which Jira task belongs to this PR?

Testing steps

Note

Might need to run locally having STRAPI env vars pointed to prod environment

  1. Check the table rendered for /learn/dex-aggregator-best-swap-rates on production compared to this branch

Why did I implement it this way?

Checklist before requesting a review

  • I have performed a self-review of my code
  • This pull request is as small as possible and only tackles one problem
  • I have added tests that cover the functionality / test the bug
  • If this changed the API, I have updated the documentation
  • I have provided QA instructions for the feature / fix implemented in this PR (if applicable)
  • I have provided instructions for any environment / deployment changes that this PR needs when merged

Summary by CodeRabbit

  • New Features

    • Tables support rich inline content (formatted text and links) with precise cell-range-aware rendering.
    • Paragraph handling improved to detect CTAs/widgets and serialize inline content into plain text with positions.
  • Bug Fixes

    • Safer paragraph text extraction and more reliable table parsing with fallback for unsupported cases.
  • Tests

    • Added tests covering inline serialization, positioned ranges, and markdown table parsing.

@mmioana mmioana self-assigned this Apr 24, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
jumper-exchange Ready Ready Preview, Comment Apr 27, 2026 8:04am
jumper-exchange-storybook Ready Ready Preview, Comment Apr 27, 2026 8:04am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 743fb8fc-ba46-4663-a9cf-cf951f1f59c7

📥 Commits

Reviewing files that changed from the base of the PR and between 81ea3da and e45c65b.

📒 Files selected for processing (1)
  • src/components/RichBlocks/renderers/TableRenderer.tsx

Walkthrough

Adds range-aware markdown table parsing and rich inline serialization/rendering so ParagraphBlock can detect tables from React inlines, compute character ranges for cells, and render formatted cell content (bold, links) by mapping ranges to positioned inline segments.

Changes

Cohort / File(s) Summary
Paragraph & Table API
src/components/RichBlocks/blocks/ParagraphBlock.tsx, src/components/RichBlocks/renderers/TableRenderer.tsx
ParagraphBlock derives safe firstText and uses range-aware parsing; TableRenderer props now accept `string
Cell & Text Renderers
src/components/RichBlocks/renderers/renderTableCellFromSegments.tsx, src/components/RichBlocks/renderers/textWithOptionalBoldMarkdown.tsx
New renderTableCellFromSegments maps character ranges to positioned inline parts and clamps slices; renderTextWithOptionalBoldMarkdown renders optional bold markdown or delegates to HTML renderer.
Inline Types & Walker
src/components/RichBlocks/utils/inlinePartTypes.ts, src/components/RichBlocks/utils/inlineNodeWalker.ts
Introduces InlinePart/PositionedPart types and a walker that converts React inlines into typed parts (text/link) and exports inlinePartsToPlainString.
Serialization / Positioning
src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.ts, src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.spec.ts
Adds serialization of React inlines to plain string and building of positioned parts with tests validating serialization, positioning, and table detection.
Markdown Table Parsing
src/components/RichBlocks/utils/tableRowUtils.ts, src/components/RichBlocks/utils/parseMarkdownTable.ts
Extracts row logic to tableRowUtils, adds per-cell char-range calculation, and introduces parseMarkdownTableWithRanges returning headers/rows plus header/data cell ranges.

Sequence Diagram

sequenceDiagram
    participant PB as ParagraphBlock
    participant S as Serializer
    participant P as TableParser
    participant TR as TableRenderer
    participant CR as CellRenderer

    PB->>S: serializeParagraphInlinesFromReact(children)
    S-->>PB: { plain, positioned[] }
    PB->>P: parseMarkdownTableWithRanges(plain)
    P-->>PB: { headers, rows, headerRanges, dataRanges }
    PB->>TR: render table with headers/rows
    TR->>CR: renderTableCellFromSegments(plain, positioned, range)
    CR->>CR: filter/clamp positioned parts → nodes
    CR->>CR: use renderTextWithOptionalBoldMarkdown / LinkRenderer
    CR-->>TR: ReactNode cell
    TR-->>PB: rendered table
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • yordanove
  • Kayanski

Poem

🐰 I nibble through markup lines,
I count the pipes and mark the signs,
Ranges traced in careful hops,
Bold and links in tidy crops,
Tables flourish — carrots, not confines! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: table rendering' directly addresses the main objective to fix table rendering for Strapi-sourced content, which is the central purpose of all the changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/strapi-table-renderer

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 24, 2026

✅ All snapshot tests passed

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (6)
src/components/RichBlocks/blocks/ParagraphBlock.tsx (1)

80-87: Minor optimization: Avoid double serialization.

serializeParagraphInlinesFromReact is called on line 81, and then buildPositionedInlineParts (which internally calls the same function) is called on line 84. This results in the inline nodes being walked twice.

♻️ Suggested refactor to avoid redundant work
   const inlineNodes = children as ReactNode[];
-  const tablePlain = serializeParagraphInlinesFromReact(inlineNodes);
-  const tableData = parseMarkdownTable(tablePlain);
+  const { plain, positioned } = buildPositionedInlineParts(inlineNodes);
+  const tableData = parseMarkdownTable(plain);
   if (tableData) {
-    const { plain, positioned } = buildPositionedInlineParts(inlineNodes);
     const work = plain.trim();
     const lead = plain.length - plain.trimStart().length;
     const withRanges = parseMarkdownTableWithRanges(work);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RichBlocks/blocks/ParagraphBlock.tsx` around lines 80 - 87,
The code double-serializes inline nodes:
serializeParagraphInlinesFromReact(inlineNodes) is called to produce tablePlain,
and buildPositionedInlineParts(inlineNodes) is called shortly after but it
internally re-runs the same serialization; remove the first redundant call and
instead call buildPositionedInlineParts(inlineNodes) once, then use its returned
plain (and positioned) to call parseMarkdownTable and
parseMarkdownTableWithRanges (or alternatively change buildPositionedInlineParts
to accept a precomputed plain string and reuse it) so the inline nodes are only
walked once.
src/components/RichBlocks/utils/tableRowUtils.ts (1)

34-37: Minor: Redundant variable assignment.

const u = t creates an alias that's immediately used. Since t is already available and not mutated, this assignment is unnecessary.

♻️ Suggested cleanup
 export const getCellCharRangesInLine = (
   t: string,
   lineStartInWork: number,
 ): CellCharRange[] => {
   const cells = splitRow(t);
   if (cells.length === 0) {
     return [];
   }
-  const u = t;
-  if (!u.includes('|')) {
+  if (!t.includes('|')) {
     return [];
   }
-  const hasLead = u[0] === '|';
-  const hasTrail = u[u.length - 1] === '|';
+  const hasLead = t[0] === '|';
+  const hasTrail = t[t.length - 1] === '|';
   const i0 = hasLead ? 1 : 0;
-  const innerT = u.slice(i0, u.length - (hasTrail ? 1 : 0));
+  const innerT = t.slice(i0, t.length - (hasTrail ? 1 : 0));

(And similarly update remaining u references to t)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RichBlocks/utils/tableRowUtils.ts` around lines 34 - 37,
Remove the redundant alias "const u = t" in tableRowUtils.ts and replace all
subsequent uses of "u" with "t" (e.g., within the early guard `if
(!u.includes('|')) { return []; }`), ensuring no logic/mutation relies on "u"
before deleting the declaration; update any other references to the variable "u"
in the same function to use "t" instead.
src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.spec.ts (1)

71-78: Consider expanding range alignment test coverage.

The test validates only the first header cell range. Consider adding assertions for other cells and data rows to ensure the range calculation works correctly across the entire table structure.

💡 Example: Additional assertions
   it('cell ranges in trimmed work string slice the same substrings as splitRow', () => {
     const work = '| A | B |\n| --- | --- |\n| 1 | 2 |';
     const p = parseMarkdownTableWithRanges(work);
     expect(p).not.toBeNull();
     expect(
       work.slice(p!.headerCellRanges[0]!.start, p!.headerCellRanges[0]!.end),
     ).toBe('A');
+    expect(
+      work.slice(p!.headerCellRanges[1]!.start, p!.headerCellRanges[1]!.end),
+    ).toBe('B');
+    expect(
+      work.slice(p!.dataRowCellRanges[0]![0]!.start, p!.dataRowCellRanges[0]![0]!.end),
+    ).toBe('1');
+    expect(
+      work.slice(p!.dataRowCellRanges[0]![1]!.start, p!.dataRowCellRanges[0]![1]!.end),
+    ).toBe('2');
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.spec.ts`
around lines 71 - 78, The test only asserts the first header cell range; extend
coverage by adding assertions that verify the remaining header cells (e.g.,
p!.headerCellRanges[1] etc.) and the data row cell ranges (e.g., p!.rows or
p!.dataCellRanges for row 0/1) slice out the expected substrings from work using
their start/end, and include checks for the separator row if applicable; use the
same slice/expect pattern as the existing assertion with the specific symbols
parseMarkdownTableWithRanges, headerCellRanges and the parsed row/cell range
properties to locate where to add these extra expects.
src/components/RichBlocks/utils/parseMarkdownTable.ts (2)

23-47: Consider extracting shared parse/validation steps to avoid drift.

Both parsing functions repeat header/delimiter validation and row-shape normalization. A shared internal parser would reduce future inconsistency risk between plain parsing and range parsing.

Also applies to: 70-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RichBlocks/utils/parseMarkdownTable.ts` around lines 23 - 47,
The header/delimiter validation and row-shape normalization logic duplicated in
parseMarkdownTable (and the other parsing function around lines 70-96, likely
parseMarkdownTableRange) should be extracted into shared helpers to prevent
drift: create small internal functions (e.g., getTrimmedLines(text: string):
string[], validateHeadersAndDelimiters(headers: string[], delimiterCells:
string[]): boolean, and normalizeRowShape(row: string[], targetLen: number):
string[]) and reuse splitRow, isDelimiterRow and compact there; update
parseMarkdownTable and parseMarkdownTableRange to call these helpers (keeping
return types like ParsedTable) so both parsing flows share the same trimming,
header/delimiter checks, and row-length normalization logic.

48-56: Avoid cloning accumulator on every row append.

Line 55 (return [...acc, padded]) reallocates the rows array for each line. A simple push loop avoids repeated copies.

♻️ Proposed allocation-friendly rewrite
-  const rows = lines.slice(2).reduce<string[][]>((acc, line) => {
-    const cells = splitRow(line);
-    if (cells.length === 0) {
-      return acc;
-    }
-
-    const padded = times(headers.length, (i) => cells[i] ?? '');
-    return [...acc, padded];
-  }, []);
+  const rows: string[][] = [];
+  for (const line of lines.slice(2)) {
+    const cells = splitRow(line);
+    if (cells.length === 0) continue;
+    rows.push(times(headers.length, (i) => cells[i] ?? ''));
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RichBlocks/utils/parseMarkdownTable.ts` around lines 48 - 56,
The rows reducer currently allocates a new array for each appended row by
returning [...acc, padded]; change this to mutate the accumulator instead—e.g.,
use a forEach or keep reduce but call acc.push(padded) and return acc—so in
parseMarkdownTable.ts (the rows calculation that calls splitRow and builds
padded from headers.length) you should avoid creating new arrays on each
iteration and simply push padded into the existing acc before returning it.
src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.ts (1)

16-18: Broaden children type to match actual runtime behavior.

Both functions explicitly handle non-array input via Array.isArray(children) ? children : [children] (lines 19 and 30), but their type signatures restrict children to arrays only. This mismatch forces unnecessary array wrapping at call sites or casts to avoid type errors.

Change the type from Array<ReactNode> | ReadonlyArray<ReactNode> to ReactNode | ReadonlyArray<ReactNode> for both serializeParagraphInlinesFromReact (line 17) and buildPositionedInlineParts (line 28) to reflect the actual implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.ts` around
lines 16 - 18, The function parameter types are too narrow: update
serializeParagraphInlinesFromReact and buildPositionedInlineParts to accept
ReactNode | ReadonlyArray<ReactNode> (instead of Array<ReactNode> |
ReadonlyArray<ReactNode>) so the signatures match the runtime handling that
wraps non-array inputs via Array.isArray(children) ? children : [children];
adjust both function declarations' children parameter types to ReactNode |
ReadonlyArray<ReactNode> to remove the need for callers to pre-wrap or cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/RichBlocks/blocks/ParagraphBlock.tsx`:
- Around line 80-87: The code double-serializes inline nodes:
serializeParagraphInlinesFromReact(inlineNodes) is called to produce tablePlain,
and buildPositionedInlineParts(inlineNodes) is called shortly after but it
internally re-runs the same serialization; remove the first redundant call and
instead call buildPositionedInlineParts(inlineNodes) once, then use its returned
plain (and positioned) to call parseMarkdownTable and
parseMarkdownTableWithRanges (or alternatively change buildPositionedInlineParts
to accept a precomputed plain string and reuse it) so the inline nodes are only
walked once.

In `@src/components/RichBlocks/utils/parseMarkdownTable.ts`:
- Around line 23-47: The header/delimiter validation and row-shape normalization
logic duplicated in parseMarkdownTable (and the other parsing function around
lines 70-96, likely parseMarkdownTableRange) should be extracted into shared
helpers to prevent drift: create small internal functions (e.g.,
getTrimmedLines(text: string): string[], validateHeadersAndDelimiters(headers:
string[], delimiterCells: string[]): boolean, and normalizeRowShape(row:
string[], targetLen: number): string[]) and reuse splitRow, isDelimiterRow and
compact there; update parseMarkdownTable and parseMarkdownTableRange to call
these helpers (keeping return types like ParsedTable) so both parsing flows
share the same trimming, header/delimiter checks, and row-length normalization
logic.
- Around line 48-56: The rows reducer currently allocates a new array for each
appended row by returning [...acc, padded]; change this to mutate the
accumulator instead—e.g., use a forEach or keep reduce but call acc.push(padded)
and return acc—so in parseMarkdownTable.ts (the rows calculation that calls
splitRow and builds padded from headers.length) you should avoid creating new
arrays on each iteration and simply push padded into the existing acc before
returning it.

In `@src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.spec.ts`:
- Around line 71-78: The test only asserts the first header cell range; extend
coverage by adding assertions that verify the remaining header cells (e.g.,
p!.headerCellRanges[1] etc.) and the data row cell ranges (e.g., p!.rows or
p!.dataCellRanges for row 0/1) slice out the expected substrings from work using
their start/end, and include checks for the separator row if applicable; use the
same slice/expect pattern as the existing assertion with the specific symbols
parseMarkdownTableWithRanges, headerCellRanges and the parsed row/cell range
properties to locate where to add these extra expects.

In `@src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.ts`:
- Around line 16-18: The function parameter types are too narrow: update
serializeParagraphInlinesFromReact and buildPositionedInlineParts to accept
ReactNode | ReadonlyArray<ReactNode> (instead of Array<ReactNode> |
ReadonlyArray<ReactNode>) so the signatures match the runtime handling that
wraps non-array inputs via Array.isArray(children) ? children : [children];
adjust both function declarations' children parameter types to ReactNode |
ReadonlyArray<ReactNode> to remove the need for callers to pre-wrap or cast.

In `@src/components/RichBlocks/utils/tableRowUtils.ts`:
- Around line 34-37: Remove the redundant alias "const u = t" in
tableRowUtils.ts and replace all subsequent uses of "u" with "t" (e.g., within
the early guard `if (!u.includes('|')) { return []; }`), ensuring no
logic/mutation relies on "u" before deleting the declaration; update any other
references to the variable "u" in the same function to use "t" instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f640639-3707-4e0f-a0b7-d4fa82047241

📥 Commits

Reviewing files that changed from the base of the PR and between 6c1bbde and 81ea3da.

📒 Files selected for processing (10)
  • src/components/RichBlocks/blocks/ParagraphBlock.tsx
  • src/components/RichBlocks/renderers/TableRenderer.tsx
  • src/components/RichBlocks/renderers/renderTableCellFromSegments.tsx
  • src/components/RichBlocks/renderers/textWithOptionalBoldMarkdown.tsx
  • src/components/RichBlocks/utils/inlineNodeWalker.ts
  • src/components/RichBlocks/utils/inlinePartTypes.ts
  • src/components/RichBlocks/utils/parseMarkdownTable.ts
  • src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.spec.ts
  • src/components/RichBlocks/utils/serializeParagraphInlinesFromReact.ts
  • src/components/RichBlocks/utils/tableRowUtils.ts

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 24, 2026

Playwright test results

passed  53 passed
flaky  2 flaky
skipped  1 skipped

Details

stats  56 tests across 11 suites
duration  4 minutes, 16 seconds
commit  e45c65b

Flaky tests

chromium › mainMenu.spec.ts › Main Menu flows › Should be able to navigate to LI.FI Scan (Qase ID: 21)
chromium › mainMenu.spec.ts › Main Menu flows › Should be able to navigate to Link3 (Qase ID: 19)

Skipped tests

chromium › themeManipulation.spec.ts › Switch between dark and light theme and check the background color › Partner theme should appear in theme menu and apply background color (Qase ID: 49)

📋 View Detailed Qase Report

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant