Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions frontend/src/components/text-format-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ export default function TextFormatToolbar({
const [fontQuery, setFontQuery] = useState('')
const [fontMenuOpenUpward, setFontMenuOpenUpward] = useState(false)
const [fontListMaxHeightPx, setFontListMaxHeightPx] = useState(224)
const [displayCount, setDisplayCount] = useState(LIST_LIMIT)
const rootRef = useRef<HTMLDivElement>(null)
const fontTriggerWrapRef = useRef<HTMLDivElement>(null)
const fontMenuRef = useRef<HTMLDivElement>(null)
const sentinelRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (!fontOpen) return
Expand All @@ -82,22 +84,43 @@ export default function TextFormatToolbar({
if (!fontOpen) {
setFontMenuOpenUpward(false)
setFontListMaxHeightPx(224)
setDisplayCount(LIST_LIMIT)
}
}, [fontOpen])

const filteredFonts = useMemo(() => {
useEffect(() => {
setDisplayCount(LIST_LIMIT)
}, [fontQuery])


const allFilteredFonts = useMemo(() => {
const q = fontQuery.trim().toLowerCase()
if (!q) return GOOGLE_FONT_FAMILIES.slice(0, LIST_LIMIT)
const out: string[] = []
for (const f of GOOGLE_FONT_FAMILIES) {
if (f.toLowerCase().includes(q)) {
out.push(f)
if (out.length >= LIST_LIMIT) break
}
}
return out
if (!q) return GOOGLE_FONT_FAMILIES
return GOOGLE_FONT_FAMILIES.filter((f) => f.toLowerCase().includes(q))
}, [fontQuery])

useEffect(() => {
if (!fontOpen) return
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setDisplayCount((prev) =>
Math.min(allFilteredFonts.length, prev + LIST_LIMIT),
)
}
},
{ rootMargin: '200px' },
)
if (sentinelRef.current) observer.observe(sentinelRef.current)
return () => observer.disconnect()
}, [fontOpen, allFilteredFonts.length])
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 28, 2026

Choose a reason for hiding this comment

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

P2: IntersectionObserver effect misses sentinel remounts because displayCount is not in dependencies, which can break continued infinite loading.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/text-format-toolbar.tsx, line 116:

<comment>IntersectionObserver effect misses sentinel remounts because `displayCount` is not in dependencies, which can break continued infinite loading.</comment>

<file context>
@@ -82,22 +84,43 @@ export default function TextFormatToolbar({
+    )
+    if (sentinelRef.current) observer.observe(sentinelRef.current)
+    return () => observer.disconnect()
+  }, [fontOpen, allFilteredFonts.length])
+
+
</file context>
Suggested change
}, [fontOpen, allFilteredFonts.length])
}, [fontOpen, allFilteredFonts.length, displayCount])
Fix with Cubic




const displayedFonts = useMemo(() => {
return allFilteredFonts.slice(0, displayCount)
}, [allFilteredFonts, displayCount])

useLayoutEffect(() => {
if (!fontOpen) return
if (!fontTriggerWrapRef.current) return
Expand Down Expand Up @@ -130,7 +153,7 @@ export default function TextFormatToolbar({
window.removeEventListener('resize', syncPlacement)
window.removeEventListener('scroll', syncPlacement, true)
}
}, [fontOpen, fontQuery, filteredFonts.length])
}, [fontOpen, fontQuery, displayedFonts.length])

return (
<FloatingToolbarShell
Expand Down Expand Up @@ -176,7 +199,7 @@ export default function TextFormatToolbar({
role="listbox"
aria-label="Google Fonts"
>
{filteredFonts.map((name) => (
{displayedFonts.map((name) => (
<li key={name} role="none">
<button
type="button"
Expand All @@ -196,8 +219,16 @@ export default function TextFormatToolbar({
</button>
</li>
))}
{displayCount < allFilteredFonts.length && (
<div
ref={sentinelRef}
style={{
height: (allFilteredFonts.length - displayCount) * 32,
}}
/>
)}
</ul>
{filteredFonts.length === 0 ? (
{allFilteredFonts.length === 0 ? (
<p className="px-3 py-4 text-center text-xs text-neutral-500">
No matches
</p>
Expand Down