Skip to content

fix: artist bio rendering on Artwork screen - #13842

Closed
anandaroop wants to merge 8 commits into
mainfrom
anandaroop/DI-346-bio-fix
Closed

fix: artist bio rendering on Artwork screen#13842
anandaroop wants to merge 8 commits into
mainfrom
anandaroop/DI-346-bio-fix

Conversation

@anandaroop

@anandaroop anandaroop commented Jul 28, 2026

Copy link
Copy Markdown
Member

This PR resolves DI-346

Assisted-by: Claude:Opus-5
Assisted-by: Claude:Sonnet-5

Description

Fixes a markdown rendering issue on Artwork screen artist bios by updating it to follow the established pattern from the Artist screen.

This is what you see when you expand the collapsed artist bio via the "Read more" link.

There are two variants to handle: listed works (the vast majority) and unlisted works (small number, but with inverted rendering — TIL)

Listed works

The common case…

Platform Before After
Android (listed)
iOS (listed)

Unlisted works

Turns out that private aka unlisted works render with this component inverted.

I would have easily missed this, thanks Claude 😅

Platform Before After
Android (unlisted)
iOS (unlisted)

PR Checklist

  • I have tested my changes on the following platforms:
    • Android.
    • iOS.
  • I hid my changes behind a feature flag, or they don't need one.
  • I have included screenshots or videos at least on Android, or I have not changed the UI.
  • I have added tests, or my changes don't require any.
  • I added an app state migration, or my changes do not require one.
  • I have documented any follow-up work that this PR will require, or it does not require any.
  • I have added a changelog entry below, or my changes do not require one.

To the reviewers 👀

  • I would like at least one of the reviewers to run this PR on the simulator or device.
Changelog updates

Changelog updates

Cross-platform user-facing changes

  • Fixes text rendering on artwork screen's artist bio

iOS user-facing changes

Android user-facing changes

Dev changes

Need help with something? Have a look at our docs, or get in touch with us.

Needed because for *unlisted* works, the artist bio card is rendered with inverted colors,
and this needs to work correctly regardless of theme.
@anandaroop anandaroop self-assigned this Jul 28, 2026
Comment thread src/app/Scenes/Artwork/Components/AboutArtist.tsx Outdated
Comment thread src/app/Scenes/Artwork/Components/AboutArtist.tsx Outdated
Comment thread src/app/Scenes/Artwork/Components/__tests__/AboutArtist.tests.tsx Outdated
@artsy artsy deleted a comment from artsyit Jul 28, 2026
anandaroop and others added 3 commits July 28, 2026 16:15
biographyBlurb(format: HTML) returns markup, and slicing/measuring it
by raw character count let tags eat the truncation budget and could
cut mid-tag or mid-attribute, breaking the markup. truncateHtml now
counts and cuts only on visible (non-tag) characters, applied to both
AboutArtist and Biography which share this truncation pattern.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
The Read More/Less toggle used palette-mobile's LinkText, which
defaults accessibilityRole to "link" — misleading for a toggle that
doesn't navigate. ReadMore set this explicitly to "button"; restore
that here.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
@github-actions github-actions Bot deleted a comment from claude Bot Jul 28, 2026
Comment thread src/app/utils/truncateHtml.ts
Comment thread src/app/Scenes/Artwork/Components/AboutArtist.tsx
Two issues from PR review: the char-by-char scanner counted entity
references (e.g. "&amp;") one character at a time, so a cut could
land mid-entity and a valid entity's raw length inflated the visible
budget. Separately, once the budget was spent the scanner kept
consuming any following tag — including a new opening tag like
<h3> — leaving it dangling open when the caller appends "... " right
after it.

Fix: treat a full "&name;"/"&#123;"/"&#x1F;" run as one visible
character in both truncateHtml and visibleHtmlTextLength, and once
the budget is spent, only continue consuming closing tags (to finish
elements already open) rather than any tag.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
@github-actions github-actions Bot deleted a comment from claude Bot Jul 28, 2026
Comment thread src/app/utils/truncateHtml.ts Outdated
Comment thread src/app/Components/HTML.tsx
Comment thread src/app/Scenes/Artwork/Components/AboutArtist.tsx
Comment thread src/app/Components/Artist/Biography.tsx
anandaroop and others added 2 commits July 28, 2026 17:00
The color prop only reached text through tagsStyles entries for
a/p/li/em/h1/h2/h3. Anything outside those — bare root text, or
tags like strong/blockquote/ol with no explicit entry — kept
react-native-render-html's default color, rendering dark-on-dark on
the inverted (unlisted) card. This is reachable today: truncateHtml
can stop right after a closing block tag, and AboutArtist appends
"... " outside any element at that point.

baseStyle applies to the whole render tree, closing the gap for
every tag at once instead of enumerating more tagsStyles entries.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
They answered the same "what counts as visible" question two
different ways — one by regex-stripping tags, the other by walking
characters with an inTag flag — and could disagree on malformed
input (e.g. an unterminated "<" tag), since only one of the two
strips it. Both callers then risked "canExpand: true" alongside a
truncatedText that was already the full string, making Read More a
no-op.

truncateHtml now returns { text, wasTruncated } from a single pass,
so the two can no longer drift apart by construction. Simplifies
both call sites, which no longer need two separate function calls.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
@github-actions github-actions Bot deleted a comment from claude Bot Jul 28, 2026
@artsyit

artsyit commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

This PR contains the following changes:

  • Cross-platform user-facing changes (Fixes text rendering on artwork screen's artist bio - anandaroop)

Generated by 🚫 dangerJS against 068cd13

Comment on lines +11 to +13
* without ever cutting in the middle of a tag, an attribute, or an entity reference
* (e.g. `&amp;`). Once the budget is spent, still emits any tags needed to close
* elements that are already open, but stops before a new tag would open.

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.

The doc comment overstates what the scanner does. It never emits closing tags — it only passes through ones already present in the source at the cut point. In the common case the budget runs out mid-element and the output has a dangling open tag:

truncateHtml("<p>Hello world</p>", 5)"<p>Hello" (your own test on line 30 of the spec asserts exactly this).

That's fine in practice since react-native-render-html's parser auto-closes, but the comment reads as a guarantee of balanced markup. Suggest wording closer to "passes through closing tags that follow the cut so already-open elements aren't left mid-list, but stops before a new tag would open."

<RenderHtml
contentWidth={contentWidth}
source={{ html }}
baseStyle={{ color: color(textColor) }}

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.

baseStyle carries only color, so text that doesn't match a tagsStyles entry gets the right color but not fontFamily: FONTS.regular. That path is reachable by design here: AboutArtist appends "... " after truncateHtml stops on a closing block tag, so the ellipsis is a root text node in the system font next to bio text in Unica77LL.

Adding fontFamily: FONTS.regular to baseStyle closes it. Don't spread variantStyles in as well — li deliberately omits lineHeight (line 92) and a baseStyle lineHeight would cascade back into it and undo that.

Comment on lines +38 to +40
const { text: truncatedText, wasTruncated: canExpand } = text
? truncateHtml(text, textLimit)
: { text: undefined, wasTruncated: false }

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.

The ternary only exists to avoid calling truncateHtml(null), and it types truncatedText as string | undefined for a value that's only read under a !!text guard. truncateHtml("", n) already returns { text: "", wasTruncated: false }:

Suggested change
const { text: truncatedText, wasTruncated: canExpand } = text
? truncateHtml(text, textLimit)
: { text: undefined, wasTruncated: false }
const { text: truncatedText, wasTruncated: canExpand } = truncateHtml(text ?? "", textLimit)

Comment on lines +98 to +118
it("renders HTML-formatted bio content without raw markdown markers", () => {
renderWithRelay({
Artwork: () => ({
isUnlisted: false,
displayArtistBio: true,
artists: [
{
biographyBlurb: {
text: "<h3>Key Exhibitions</h3><ul><li>David Hockney, The Metropolitan Museum of Art, 2017</li></ul>",
},
},
],
}),
})

expect(screen.getByText("Key Exhibitions")).toBeOnTheScreen()
expect(
screen.getByText("David Hockney, The Metropolitan Museum of Art, 2017")
).toBeOnTheScreen()
expect(screen.queryByText(/<h3>/)).not.toBeOnTheScreen()
})

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.

The unlisted/inverted variant is the reason the color prop was added to HTML, and nothing here asserts it. This test sets isUnlisted: false; the only test with isUnlisted: true (line 73) checks tracking. A dark-on-dark regression in textColor would pass CI.

HTML.tests.tsx covers the prop in isolation, so the gap is the wiring — an isUnlisted: true case asserting the bio text and the Read More link render mono0 would close it, mirroring what HTML.tests.tsx already does with toHaveStyle({ color: "#FFFFFF" }).

Also missing: expand/collapse for this component. Biography.tests.tsx has both directions; here fireEvent.press on Read More only checks the tracking payload, not that the full text appears or that Read Less re-truncates.

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review

Summary

Switches the Artwork screen's AboutArtist bio from the markdown-based ReadMore to the HTML component, matching the Artist screen's Biography, and adds format: HTML to the fragment. Adds a color prop to HTML so the inverted unlisted-artwork card renders readable text, and adds a shared truncateHtml util that budgets on visible characters rather than raw markup length.

truncateHtml is well-factored — single-pass so text and wasTruncated can't disagree, correctly placed in src/app/utils, and the spec covers the interesting cases (entities, bare &, attribute boundaries, malformed input). Making baseStyle carry the color is the right call over enumerating more tagsStyles entries.

Checked and clear: src/__generated__ is gitignored so no artifact needs committing; biographyBlurb(format: Format, partnerBio: Boolean) matches data/schema.graphql:2407; Schema.ActionNames.ReadMore / ContextModules.ArtistBiography / Flow.AboutTheArtist all exist; the tracking payload matches what the old ReadMore emitted; HTML's contentWidth (width - space(4)) still lines up with Screen.FullWidthItem p={2}; the useState/useTracking calls sit above the !artists.length early return.

I could not run yarn jest or yarn tsc here (no node_modules), so nothing below is a claim about CI.

Issues Found

Nothing blocking.

🟡 Test gap on the inverted variantsrc/app/Scenes/Artwork/Components/__tests__/AboutArtist.tests.tsx. Inline comment. The unlisted card is why the color prop exists, and no test asserts the wiring; expand/collapse for this component is also untested, where Biography.tests.tsx covers both directions.

🟢 baseStyle sets color but not fontsrc/app/Components/HTML.tsx:53. Inline comment. Root-level text (the "... " AboutArtist appends after a closing block tag) misses fontFamily: FONTS.regular.

🟢 Doc comment overstates the closing-tag behaviorsrc/app/utils/truncateHtml.ts:11-13. Inline comment. Closing tags are passed through, not synthesized; the usual output has a dangling open tag.

🟢 Unneeded ternarysrc/app/Scenes/Artwork/Components/AboutArtist.tsx:38-40. Inline comment, with a suggestion.

🟢 Biography's toggle still has no accessibilityRolesrc/app/Components/Artist/Biography.tsx:36. Commit 6af6ea2 added accessibilityRole="button" to the Artwork screen's toggle because it doesn't navigate. The Artist screen's <Text underline onPress={...}> has the same problem and the same fix. (Couldn't attach inline — the line isn't in the diff.)

Areas Reviewed

Bugs & edge cases. One behavior worth knowing about, not worth changing: wasTruncated goes true if anything at all follows the budget, including trailing whitespace. truncateHtml("<p>Hello world</p> ", 11) returns the full <p>Hello world</p> with wasTruncated: true, so Read More would reveal a space. It needs the visible length to land exactly on the boundary, so it's unlikely on real bios.

Entity handling reads correct — truncateHtml("Arts &amp; Crafts", 7) counts &amp; as one char and stops at "Arts &amp; ", and the bare-& fallback works because a regex miss falls through to the plain-character branch.

Performance. The scan breaks at the budget, so it's bounded by ~140 visible chars plus markup rather than the full bio. No memoization needed.

Architecture. truncateHtml in src/app/utils with both scenes importing from there is the right shape — no cross-scene import. ReadMore is still used elsewhere, so nothing is orphaned.

Questions for Author

  1. The old ReadMore wrapped the collapsed content in a TouchableWithoutFeedback with accessibilityLabel="Read More" (ReadMore.tsx:203-216), so tapping anywhere on the bio expanded it. Now only the link is tappable, and LayoutAnimation.configureNext on toggle is gone. Both look like deliberate parity with Biography, which has neither — confirming.

  2. AboutArtist renders "Read More" on its own line below the text; ReadMore spliced "... Read more" inline into the last text node. The screenshots show the new layout, so I read this as intended — just flagging that it's a layout change beyond the markdown fix.

Screenshots on both platforms and the LLM assistance disclosure are both present.

@anandaroop

Copy link
Copy Markdown
Member Author

Closing in favor of #13851

@anandaroop anandaroop closed this Jul 30, 2026
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.

2 participants