view! has no animation support, and long lists mean dropping out of the markup into raw GPUI calls. This issue covers both. They can ship separately, but they meet at animated list rows, so they're designed together.
Part 1: motion
Problem
To animate anything today, an app has to leave view!, call a motion library by hand and wrap the built element. gpui-motion already provides declarative, interruptible animation for GPUI: springs, enter and exit, keyed presence, layout, text and gestures. It respects the user's reduced-motion setting. view! should make it a first-class part of the markup.
GPUI version constraint
Motion's native types must come from the same GPUI as Rake, or they won't fit together:
| Crate | GPUI it uses |
|---|---|
gpui-ce-motion 0.4.0 | gpui-ce =0.2.2 from crates.io |
gpui-motion-kit 0.5.0 | gpui-kit =0.7.3, on Kit's gpui-pre backend |
| Rake today | gpui_ce from Git at rev 99109e3 |
| Rake after its planned move | gpui-pre 0.3.11 through gpui-base 0.9.0 |
Neither motion crate matches Rake today, even the CE one, because a Git source and a crates.io release are different crates to Cargo. Integrate after Rake moves to gpui-pre, through gpui-motion-kit. Release a gpui-motion-kit version on the same gpui-pre and Kit versions Rake pins.
Generated code should depend only on Rake paths, such as rake::motion, so apps get motion through a Rake feature and never pick a GPUI version themselves.
Proposed syntax
Start with an attribute that takes a frame the app already computed:
let card = self.card_motion.frame(
Motion::new(Target::default().width(width)).initial(Target::default().opacity(0.0)),
window,
cx,
);
rake::view! {
<div id="card" class="p-4 rounded-lg bg-surface" motion={card}>
"Click to resize"
</div>
}- The macro applies the frame to the element it builds, and nothing else. State stays in the app's view, as with the rest of
view!, and the macro never relies on acxorwindowin scope. - Allowed on built-in layout tags and buttons. Components opt in by accepting a frame property.
- Keyed enter and exit in loops (keyed presence) uses each row's explicit stable ID, which loops already require. Removed rows stay until their exit animation finishes.
- Shorter forms, such as
motion:animate={target}, can come later once real use shows what's common.
Inspection and agents
- Inspection and the CDP agent client should report the animated, on-screen bounds, and say an element is animating. Agents should wait for motion to settle before acting on it.
rake ctland test helpers should be able to turn motion off, so automated runs and screenshots are deterministic.
Part 2: virtualized lists
Problem
Long lists mean writing gpui::uniform_list or gpui::list by hand inside view!. docs/experimental-overlays.md shows the pattern, and examples/view/src/documents.rs uses it. The app must give the viewport a fixed height, keep its own scroll handle and, for rows of different heights, keep a ListState and update it by hand on every insertion and deletion. The docs say "the macro adds no reconciliation for those APIs", so this is easy to get wrong.
Proposed syntax
rake::view! {
<list id="documents" class="h-[480px]"
items={&self.documents}
key={|doc| doc.id}
state={&self.documents_list}>
{|doc, index| rake::view! {
<div class="px-3 py-2">{doc.title.clone()}</div>
}}
</list>
}itemsandkeyreplace hand-written ranges. The key must be stable, and the generated row ID comes from it, so rows don't need their own ID.stateis a retainedrake::ui::ListStatestored on the app's view. It owns scrolling and, for variable heights, compares keys between renders to update GPUI'sListState. This is the missing reconciliation.- Fixed-height rows (
row-height="32px") useuniform_list. Otherwise uselist. - Built-ins: an empty-state slot,
scroll_to_key, keeping the scroll position when items are inserted above, and keyboard navigation. - A compile error or debug check when the viewport has no bounded height, since that silently breaks virtualization.
Accessibility and inspection
- The list has the list role, and each row has the list item role. The assistive tree should report the full item count and each row's position, not only the rows on screen. Check what GPUI's AccessKit integration supports.
- Inspection and CDP queries should say the list is virtualized and give its total count. An agent should be able to scroll a row into view by its key before acting on it.
Relationship to GPUI Kit
After the move, check gpui-base and gpui-component's list support first. If they already handle keyed, virtualized lists well, <list> should build on them instead of on raw GPUI.
Where they meet
Rows in a <list> can take motion, so inserts, removals and reorders animate using the list's keys. Exit animations and reorder movement for rows that scroll off-screen need a clear rule. A first version can animate only rows that are on screen.
Out of scope
- Grids and tables with column virtualization.
- Gesture-driven reordering inside virtualized lists. That's a follow-up once both parts exist.
- Animation in browsers is not out of scope, but measure its cost before enabling it everywhere.
Done when
- Motion:
motion={...}works on built-in tags in native and Web builds, keyed presence works in loops, and reduced motion is respected. Inspection reports the animated bounds, andrake ctlcan turn motion off. An example inexamples/view,docs/experimental-view.mdupdated and macro snapshot tests added. - Lists:
<list>handles fixed and variable heights with key-based updates. Tests cover inserting, removing and reordering, keeping the scroll position, the empty state and an unbounded-height error.examples/view/src/documents.rsuses<list>, and a benchmark with at least 100,000 rows is recorded. - Accessibility: total count and row positions are checked with a real screen reader on at least one platform, and the results are recorded.
Depends on
- The move from GPUI CE to
gpui-pre. - A
gpui-motion-kitrelease on thegpui-preand Kit versions Rake pins.
Activity
0No activity yet.