Recipes
Chat, accordions, deep links, and shrink-to-fit bubbles.
AI chat with thinking & steps
A modern assistant transcript is a stress test for a virtualizer: every turn is a
different height, reasoning traces collapse and expand, and the newest turn keeps
growing as it streams. It maps cleanly onto mugen — each turn is a pure
item → tree using all three hooks. useMugenState collapses the “Thought for…”
trace (toggling re-measures just that row); useMugenEffect streams the final
turn's answer in word by word; and stickToBottom follows it down with a smooth
spring — scroll up to break free, scroll back to re-engage. It opens at the latest
turn (initialScroll="bottom"); hit Replay to watch it stream again, or expand
a reasoning trace and watch the list re-flow without losing your place:
Bidirectional pagination
Top and bottom slots are measured like row content, so skeleton loaders can live
inside the scrollable coordinate system without throwing off row offsets.
onTopReached and onBottomReached fire once when the scroll position enters
their threshold, which is a clean fit for cursor pagination in either direction.
This audit-log feed opens in the middle of a loaded page; hit either edge and the
slot swaps in shimmering skeletons while the page resolves, then settles to the
exact height of the prepended/appended variable-height rows — no jump. Idle edges
show a soft hint, exhausted edges an end-cap:
Rich markdown rows
@wingleeio/mugen-markdown
parses each row's markdown with incremark and renders
it with mugen primitives — so headings, lists, fenced code, tables, and inline
bold / code / links are all measured by the
walker. Off-screen rows have exact heights with no measure-on-mount shift, even
though every row is a different mix of blocks. Inline marks are styled through a
deep-partial theme; block nodes are overridable with a fully-typed components
map (here, h1 gets an accent rule) — both authored from the same primitives, so
they stay measurable:
Tooltips, popovers & dialogs in rows
Overlays are the case that breaks a virtualizer: a tooltip or dropdown is a real
React component (hooks, raw DOM, a portal), so the walker can't measure it — and
would throw if it tried. Escape
makes that a non-issue: it reserves a fixed-size box in the row that the walker
never looks inside, so a stock shadcn/ui or Radix
widget drops in — trigger included — with no mugen-specific wrapper. The
floating half is portaled to document.body by Radix itself, where mugen's
layout never sees it. And short labels like a name or a role never wrap, so they
don't need pretext either: plain styled DOM inside the declared box is exact by
construction. This 800-row directory gives every row a shadcn Tooltip (hover
the name), Popover (React), DropdownMenu (arrow-key menu), and a modal
Dialog (Details) — open any of them and the row heights never budge:
The contract is foreignObject's: you declare the box (height, optional
width), and you design the children within it — content that outgrows the box
is clipped, never silently re-measured. Keep Escape for known-footprint
content; for a tooltip on measured, wrapping text, build a custom primitive
whose measure() is measureChildren and whose render adds the trigger
handlers — the walker measures the children exactly, and the floating panel
portals out as usual.
Chat / message list
Avatar on the left, author + message on the right. The avatar is a fixed-size box
(width/height); the text column grows into the remaining width — and every row
is a different height.
Accordion rows
useMugenState controls whether the body renders. Toggling re-measures the row.
Click a question:
Deep link into a row
Open a specific row's state and scroll to it — the scroll lands pixel-exact because the off-screen height is already known. Drive expansion from the item data so it works before the row ever mounts:
import { , , , } from '@wingleeio/mugen';
interface Thread {
: string;
: string;
: string;
: boolean; // expansion lives in the data
}
export function ({ , }: { : Thread[]; ?: string }) {
const = ({ : });
const = (: string) => {
// flip `open` for this thread in your own state, then:
.(, { : 'smooth', : 'center' });
};
if () ();
return (
<
={}
={() => .}
="15px Inter"
={22}
={() => (
< ={4} ={12}>
< ="600 15px Inter">{.}</>
{. ? <>{.}</> : null}
</>
)}
/>
);
}See Scrolling for the full scrollToItem API.
Shrink-to-fit bubbles
Chat bubbles that hug their text instead of filling the column:
import { , } from '@wingleeio/mugen';
interface Msg {
: string;
: string;
}
export function (: Msg) {
return (
< ={10}>
< >{.}</>
</>
);
}shrink measures the text at its natural (tightest) width via pretext's
naturalWidth, and renders with width: fit-content so the browser shrink-wraps
to the same width.