vercel-labs / vercel-labs/react-native-diffs
DiffsView renders at 0 height in content-sized parents (workaround for mrousavy/nitro#1199)
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 16
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Summary
DiffsView requires its parent to supply explicit dimensions (typically flex: 1 or a fixed height). When embedded in a content-sized parent — e.g. a chat bubble or a <View> with no flex — it renders at 0 height and content is invisible.
Reproduce
<View style={{ padding: 16 }}>
<DiffsView content="```ts\nconst x = 1;\n```" colorScheme="light" />
</View>
Expected: bubble grows to fit the rendered code block.
Actual: bubble has 0 height.
Root cause
Nitro 0.35 does not propagate the underlying UIView's intrinsic size to Yoga. Confirmed by reading the nitrogen output:
nitrogen/generated/shared/c++/views/HybridDiffsComponent.hppdeclares the shadow node as a plainConcreteViewShadowNode<...>, without theMeasurableYogaNode/LeafYogaNodetraits.- Yoga's
LayoutableShadowNode::measureContentdefault returns zero, and is only consulted whenMeasurableYogaNodeis set on the shadow node traits (react-native YogaLayoutableShadowNode.cpp).
The framework-level fix is tracked at mrousavy/nitro#1199 — Support measureContent(...) for Nitro Views, opened by Marc Rousavy himself. As of writing it has 0 comments and no implementation.
Until Nitro adds measureContent, every Nitro view has the same limitation. The pragmatic mitigation at the consumer-library level is to surface the measured height through a callback so the JS parent can apply it as inline style.height.
Use case
Embedding DiffsView in a chat-style transcript: one Claude assistant turn per "bubble", each bubble's height driven by the rendered markdown. flex: 1 is not applicable because bubbles must be content-sized so they stack and the parent ScrollView can scroll across multiple turns.
Proposed solution
Add an optional callback prop to DiffsView:
export interface ContentSize {
width: number;
height: number;
}
export interface DiffsProps extends HybridViewProps {
// ... existing props
onContentSizeChange?: (size: ContentSize) => void;
}
Implementation outline (iOS):
- Outer
viewbecomes a smallUIViewsubclass that fires a callback onlayoutSubviewsbounds change. - After
setMarkdown(switched tosetMarkdownManuallyfor synchronous Core Text layout), callmarkdownTextView.boundingSize(for: width)and invoke the callback. - Coalesce same-size reports to avoid feedback loops when JS reflows on each report.
Caller pattern (sidecode wrapper, simplified):
function MarkdownView(props) {
const [height, setHeight] = useState<number>();
return (
<DiffsView
{...props}
onContentSizeChange={{ f: (size) => setHeight(size.height) }}
style={{ height }}
/>
);
}
Backward compatible: existing flex: 1 consumers (e.g. v0.app/ios) are not affected; the new prop is opt-in.
Next step
Happy to submit a PR with the spec change, iOS implementation, and an updated example/ showcasing the chat-bubble pattern. Reference the work I've already done in a private fork — full diff is small (~50 LOC excluding nitrogen-generated output). Would you accept this approach?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read nitrogen/generated/shared/c++/views/HybridDiffsComponent.hpp and the existing DiffsView spec and iOS implementation. Review the Nitro #1199 limitation, then inspect the example/ entry point for the chat-bubble use case. Done means the optional content-size callback reports coalesced dimensions, existing flex consumers remain unaffected, and the example demonstrates content-sized rendering.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, typescript
- Domain
- frontend, mobile-dev
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100