@@ -19,7 +19,8 @@ interface SommelierCardProps {
1919}
2020
2121// Format feedback: split into paragraphs
22- function formatFeedback ( text : string ) : React . ReactNode [ ] {
22+ // Uses stable keys based on content hash to prevent React DOM reconciliation errors
23+ function formatFeedback ( text : string ) : React . ReactNode {
2324 // Split into sentences - but only at sentence boundaries
2425 // A sentence ends with .!? followed by space and uppercase letter
2526 // This avoids breaking "Next.js", "e.g.", "i.e.", etc.
@@ -39,9 +40,16 @@ function formatFeedback(text: string): React.ReactNode[] {
3940 }
4041 } ) ;
4142
42- return paragraphs . map ( ( p , idx ) => (
43- < p key = { idx } className = { idx > 0 ? 'mt-3' : '' } > { p } </ p >
44- ) ) ;
43+ // Wrap in a single container to avoid DOM reconciliation issues when toggling expansion
44+ // This prevents the "insertBefore" error caused by React trying to reconcile
45+ // multiple paragraph elements when the parent's className changes
46+ return (
47+ < div className = "space-y-3" >
48+ { paragraphs . map ( ( p , idx ) => (
49+ < p key = { `feedback-${ idx } -${ p . slice ( 0 , 20 ) . replace ( / \s / g, '' ) } ` } > { p } </ p >
50+ ) ) }
51+ </ div >
52+ ) ;
4553}
4654
4755export function SommelierCard ( {
@@ -102,7 +110,11 @@ export function SommelierCard({
102110
103111 { /* Feedback content */ }
104112 < div className = "p-5" >
105- < div className = { `text-gray-700 leading-relaxed ${ isExpanded ? '' : 'line-clamp-6' } ` } >
113+ < div
114+ className = { `text-gray-700 leading-relaxed transition-[max-height] duration-300 ease-in-out overflow-hidden ${
115+ isExpanded ? 'max-h-[2000px]' : 'max-h-36'
116+ } `}
117+ >
106118 { formatFeedback ( feedback ) }
107119 </ div >
108120
0 commit comments