Skip to content

fix: render inline markdown in product list descriptions - #603

Merged
alukach merged 6 commits into
mainfrom
fix/product-list-markdown
Oct 1, 2026
Merged

alukach merged 6 commits into
mainfrom
fix/product-list-markdown

Conversation

@alukach

@alukach alukach commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Carries on #602 by @isaaccorley, whose fork doesn't allow maintainer edits. Product list descriptions now render their inline Markdown — paragraphs, bold, italics, strikethrough, inline code and lists — clamped to three lines. The rest is reduced for a card: headings and links render as plain text, because a listing links only within Source (to the product and its publisher). Tables and fenced code blocks are left out, since they have no readable inline form. The full description, links included, stays on the product page.

ProductListItem is a client component, so it renders with react-markdown directly, using remark-gfm, an allowedElements list and unwrapDisallowed. It doesn't go through MarkdownViewer: that pulls in Bright, which is server-only, so importing it here fails next build. A description with a code fence would also hit React's async-client-component error at render. The clamp is CSS line-clamp in ProductList.module.css. jest.config.cjs adds react-markdown's and remark-gfm's ESM-only dependencies to the transform allowlist, because the story smoke test now loads them. The Bright and rehype-raw entries #602 also added are gone, since nothing in a story imports MarkdownViewer. Mocking react-markdown instead would hollow out the play assertions the smoke test runs.

Story fixtures use fictional jazz-themed accounts and products (acoltrane, tmonk, mdavis) with example.org links.

Stories

Links as text

Table description

README description, clamped

Testing

npm run type-check passes, and npx jest passes (82 suites, 867 tests, after merging main, including the story smoke test). npm run build compiles; it then stops prerendering /, which needs AWS credentials I don't have locally. The play tests for Markdown Description, Table Description and Readme Description pass in local Storybook. The Readme story's clamp assertion only runs in a browser, because jsdom has no layout. The one accessibility violation on these stories is the existing yellow "Unlisted" badge's contrast (4.07:1).

Docs

Checked docs.source.coop: docs/using-source/create-a-data-product.md calls the description "a short overview" and points detailed documentation to the README. That still holds, and no page describes how listings render descriptions. No data-proxy ADR is involved.

🤖 Generated with Claude Code

isaaccorley and others added 2 commits September 30, 2026 14:59
Listings link only to places within Source (the product and its publisher), so links in a Markdown description are unwrapped to their text while the rest of the formatting is kept. MarkdownViewer gains a disallowedElements passthrough to react-markdown for this, and the list item's Text renders as a div instead of wrapping one via asChild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
source-coop-ui Ready Ready Preview Oct 1, 2026 4:58am UTC
source-cooperative Ready Ready Preview Oct 1, 2026 4:58am UTC

Request Review

@claude

claude Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 16s —— View job


✅ No blocking issues — safe to merge.

I read the diff for ProductListItem.tsx, ProductList.module.css and jest.config.cjs. I didn't run the code, type-check or jest.

  • Markdown handling:
    • Raw HTML isn't parsed, because react-markdown ignores it by default.
    • Links and headings are unwrapped to their text.
    • Images have no children, so they drop out.
    • Tables and pre blocks are replaced with null, which also drops their descendants. The comment above DESCRIPTION_ELEMENTS explains why they have to be allowed first.
    • I found no XSS or link-injection path.
  • Clamp: -webkit-box with line-clamp: 3 is the standard approach. The trailing margin-bottom on the last <p> or list sits inside the clamped box, so it may add a few pixels of gap. That is cosmetic.

Simplify (ponytail)

  • jest.config.cjs:23: the allowlist now has about 15 more package names to maintain. If it keeps growing, replace it with a catch-all such as /node_modules/(?!.*\\.mjs$). Better still, use moduleNameMapper to stub react-markdown in the story smoke test. Not needed now.
  • ProductListItem.tsx:24-27: DESCRIPTION_COMPONENTS plus the table and pre entries in the allow-list is the smallest way to drop those blocks with react-markdown. I would keep it as is.

Docs

The PR description names docs/using-source/create-a-data-product.md and says why it still holds. It also says no data-proxy ADR is involved. That satisfies the CLAUDE.md requirement.


💰 Estimated review cost: $0.11 · 0m15s · 4 turns

ProductListItem is a client component, so importing MarkdownViewer pulled Bright (server-only) into the client bundle and failed the build; a description with a fenced code block would also have hit the async-component error at render. Listings now render the description with react-markdown directly, allowing only inline elements (paragraphs, emphasis, inline code, lists) and unwrapping the rest — headings, tables, code blocks and links read as plain text. The description is clamped to three lines. MarkdownViewer's disallowedElements prop is removed, as nothing uses it.

Story fixtures use fictional jazz-themed accounts and products, and a README-length description story checks the block elements collapse and the clamp truncates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parse descriptions with GFM so tables and strikethrough are recognized, then render tables and fenced code blocks as nothing in listings: neither has a readable inline form, and both are on the product page. Adds a story for a description with a table alongside headings, emphasis, strikethrough, inline code, a list and a link, and makes the README story's clamp assertion skip under jsdom, which has no layout. Removes the committed screenshot of a real product; PR screenshots live on an assets branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alukach alukach changed the title fix: render markdown in product list descriptions, with links as text fix: render inline markdown in product list descriptions Oct 1, 2026
@alukach
alukach requested a review from jedsundwall October 1, 2026 04:54
@alukach
alukach marked this pull request as ready for review October 1, 2026 04:55
Drop the Bright and rehype-raw dependencies that #602 added to the jest transform allowlist; no story imports MarkdownViewer any more, so only react-markdown and remark-gfm's ESM dependencies need transforming. Say directly why table and pre are allowed in listings: left disallowed, unwrapDisallowed would spill their text into the card. Both from review on #603.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

I agree with this approach. Although I wonder if we should go ahead and only allow plain text in the description and no Markdown at all, whether or not we display it on a product detail page or in a list.

@jedsundwall

Copy link
Copy Markdown
Contributor

That is to say, go ahead and push this for now. I'll keep thinking about this…

@alukach
alukach merged commit 7e9a94a into main Oct 1, 2026
9 checks passed
@alukach
alukach deleted the fix/product-list-markdown branch October 1, 2026 05:09

This branch was successfully deployed

2 active deployments
Preview – source-cooperative — 2ead97db Deployed Oct 1, 2026 by vercel[bot]
Preview – source-coop-ui — 2ead97db Deployed Oct 1, 2026 by vercel[bot]
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.

3 participants