rustmotion-studio cannot be published to crates.io. It depends on
dioxus-primitives and dioxus-attributes via a git dependency pinned at
DioxusLabs/components@02801f27, and cargo refuses to publish any crate
carrying a git dependency. The crate therefore ships publish = false and
there is no cargo install rustmotion-studio.
That dependency will not become publishable: dioxus-primitives on crates.io
is version 0.0.0, a name reservation with no code, untouched since
2025-04-13.
This chantier rewrites the studio's view layer onto
gpui-kit (gpui-kit = "=0.6.6", Apache-2.0), whose
entire dependency tree is on crates.io.
De-risking already done
| Check |
Result |
| gpui-kit resolves and compiles on macOS |
854 packages, 451 rlibs, 49 s debug |
| git dependencies in the gpui-kit tree |
0 (vs 2 today) |
| Existing studio test suite |
128 green (121 unit + 7 integration), 2 ignored |
| Components needed vs. offered by the kit |
all present |
Scope
Goal: Rewrite the studio's view layer onto gpui-kit so that
rustmotion-studio becomes publishable on crates.io, with
light/dark/system themes and feature parity.
Invariants: - The 128 existing tests stay green at every integration.
- `Shared`/`StudioModel`, `Mutation`, the self-write ledger, the
disk-rebased debounce, undo/redo, the diff, the frame cache and
the schema-driven property registry are NOT modified.
- Zero git dependencies in the final tree.
Out of scope: New features. A timeline editor. Windows/Linux support.
Any change to the render engine (crates/rustmotion*).
Acceptance: - `cargo publish -p rustmotion-studio --dry-run` passes
- `grep -c 'source = "git' Cargo.lock` returns 0
- 128 existing tests green, plus the new ones
- The studio opens, plays, edits, exports; the theme follows the OS
Verify (exit): cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
The line of fracture
Of ~12 275 lines of Rust, roughly 5 200 are framework-independent and must
not be touched — all the intelligence (optimistic editing, self-write ledger,
debounce rebased onto fresh disk content, undo/redo, diff, frame cache, audio
clock, schema-derived property registry) already lives outside the UI
framework, as pure functions over plain data, covered by unit tests that mount
no Dioxus runtime.
What gets rewritten is ~5 750 lines of Rust and ~2 440 of CSS.
Net deletion: 7 of the 13 in-house components (input, popover,
separator, sheet, sidebar, skeleton, tooltip) have zero usages
outside components/ — scaffold that was never wired up. sidebar alone is
839 lines of Rust and 855 of CSS. The whole src/components/ tree is deleted;
gpui-kit is used directly, with no wrapper layer. assets/dx-components-theme.css
(70 lines) is referenced from no Rust file and goes too.
Frozen contracts
Read-only for every workstream. The only genuinely new one is the state model:
in Dioxus it is ten scattered Signal<T>, in gpui it must be one owned struct.
// src/app/state.rs — orchestrator-owned, no workstream writes it
pub struct StudioState { shared: Shared, library: SharedLibrary,
view: View, theme_pref: ThemePref }
pub struct EditorState {
current: u32, playing: bool, muted: bool, rev: u64,
selected: Option<Selection>, // replaces (u32, String, String)
show_annotations: bool, show_hits: bool,
diff_active: bool, diff_side: DiffSide, preview_scale: u16,
frame: Option<Arc<RenderImage>>, // drop_image on replacement
}
pub struct Selection { node_id: u32, pointer: String, kind: String }
pub enum ThemePref { Light, Dark, System }
Unchanged and write-forbidden: scenario/* (1 817 l.), properties.rs
(1 306), prefetch.rs (813), audio.rs (276), library/data.rs (315).
frames.rs accepts additive changes only (render_frame_rgba).
Three API facts that shape the work
1. JPEG encoding disappears. It existed only to feed the webview. gpui
takes RGBA directly via img(ImageSource::Render(Arc<RenderImage>)). No
encoding, no software cache, and a stale-cache frozen preview is structurally
impossible — every RenderImage gets a fresh monotonic id. Three traps:
- BGRA, straight alpha — swap R/B, and un-premultiply (Skia is
premultiplied by default) or render opaque.
cx.drop_image(old, Some(window)) on every swap — the GPU atlas has
no eviction; each 1920x1080 frame allocates its own ~8 MB Metal texture.
At 60 fps without dropping that is ~500 MB/s of leaked VRAM.
- Never use
surface() — it assert_eq!s on NV12 YUV and panics on BGRA.
2. The System theme does not exist in the kit. ThemeMode has only
Light | Dark, theme::init forces Light, and nothing observes the OS
appearance. It is built here, and the preference is persisted next to the
recents in ~/.config/rustmotion/.
3. Percent-coordinate overlays work natively —
.absolute().left(relative(0.25)) — so the hit-test overlay maps over
directly.
Workstreams
| # |
Mission |
Owned files |
| 1 |
Foundation: boot, window, Root + the 3 overlay layers, light/dark/system theme, persisted |
app/root.rs, app/window.rs, theme/; deletes components/** + assets/ |
| 2 |
Frame surface: RGBA->BGRA->RenderImage, drop_image discipline, request_animation_frame loop, prefetch publisher |
editor/surface.rs, additive in frames.rs |
| 3 |
Library: sidebar, search, thumbnail grid |
library/view.rs |
| 4 |
Editor shell: topbar, canvas, hit overlay, transport, shortcuts |
editor/view.rs, topbar.rs, overlay.rs, view part of playback.rs |
| 5 |
Inspector (2 225 l.), driven by the property registry, which does not move |
editor/inspector/** |
| 6 |
Diff, annotations, export notifications |
diff_panel.rs, annotations.rs, toast of export.rs |
Orchestrator-owned convergence points — no workstream writes these:
Cargo.toml, lib.rs, the four mod.rs, .github/workflows/ci.yaml and
publish.yaml (dropping GTK/WebKit/xdo), CLAUDE.md.
Regenerated at integration, never hand-edited: Cargo.lock, via
cargo check --workspace.
Schedule — pipeline, not barrier:
Batch 1: WS-1 (everything depends on it)
Batch 2: WS-2 || WS-5 || WS-6 (none depends on another)
Batch 3: WS-3 || WS-4 (as soon as WS-2 lands, without waiting for 5 and 6)
Each agent gets its own CARGO_TARGET_DIR; otherwise cargo serializes the
builds however well the sources are partitioned.
Known risks, and how they are contained
Root::render_dialog_layer / _sheet_layer / _notification_layer are
removed in 0.7.0, with no documented replacement — and they are exactly what
WS-1 and WS-6 need. Contained by pinning gpui-kit = "=0.6.6" and isolating
those three calls in a single file, so the 0.7 migration is a localised patch
rather than a hunt.
The =0.3.6 pin on gpui-pre is viral. Every kit crate pins it exactly,
so a version bump moves the whole graph in lockstep, with no overlap window.
Breaking changes land in patch slots — 0.6.2 removed the dock tiles canvas.
These are 0.x crates with no declared semver policy and no MSRV.
Accepted deliberately: gpui-kit depends on gpui-pre, a third-party snapshot
of Zed's gpui at commit bcf6582 published by the kit's maintainer, not on
Zed's official gpui. There is no alternative — official gpui is 0.2.2 and
has been frozen since 2025-10-22. gpui-component, the substance of the kit,
is mature: first published 2025-02, 30 releases, 133k downloads. It is the
gpui-kit umbrella that is young (crate name created 2026-09-03).
Side benefit
Dioxus desktop is the only reason ci.yaml and publish.yaml install
libwebkit2gtk-4.1-dev, libgtk-3-dev and libxdo-dev. Those go away.
rustmotion-studiocannot be published to crates.io. It depends ondioxus-primitivesanddioxus-attributesvia a git dependency pinned atDioxusLabs/components@02801f27, and cargo refuses to publish any cratecarrying a git dependency. The crate therefore ships
publish = falseandthere is no
cargo install rustmotion-studio.That dependency will not become publishable:
dioxus-primitiveson crates.iois version
0.0.0, a name reservation with no code, untouched since2025-04-13.
This chantier rewrites the studio's view layer onto
gpui-kit (
gpui-kit = "=0.6.6", Apache-2.0), whoseentire dependency tree is on crates.io.
De-risking already done
Scope
The line of fracture
Of ~12 275 lines of Rust, roughly 5 200 are framework-independent and must
not be touched — all the intelligence (optimistic editing, self-write ledger,
debounce rebased onto fresh disk content, undo/redo, diff, frame cache, audio
clock, schema-derived property registry) already lives outside the UI
framework, as pure functions over plain data, covered by unit tests that mount
no Dioxus runtime.
What gets rewritten is ~5 750 lines of Rust and ~2 440 of CSS.
Net deletion: 7 of the 13 in-house components (
input,popover,separator,sheet,sidebar,skeleton,tooltip) have zero usagesoutside
components/— scaffold that was never wired up.sidebaralone is839 lines of Rust and 855 of CSS. The whole
src/components/tree is deleted;gpui-kit is used directly, with no wrapper layer.
assets/dx-components-theme.css(70 lines) is referenced from no Rust file and goes too.
Frozen contracts
Read-only for every workstream. The only genuinely new one is the state model:
in Dioxus it is ten scattered
Signal<T>, in gpui it must be one owned struct.Unchanged and write-forbidden:
scenario/*(1 817 l.),properties.rs(1 306),
prefetch.rs(813),audio.rs(276),library/data.rs(315).frames.rsaccepts additive changes only (render_frame_rgba).Three API facts that shape the work
1. JPEG encoding disappears. It existed only to feed the webview. gpui
takes RGBA directly via
img(ImageSource::Render(Arc<RenderImage>)). Noencoding, no software cache, and a stale-cache frozen preview is structurally
impossible — every
RenderImagegets a fresh monotonic id. Three traps:premultiplied by default) or render opaque.
cx.drop_image(old, Some(window))on every swap — the GPU atlas hasno eviction; each 1920x1080 frame allocates its own ~8 MB Metal texture.
At 60 fps without dropping that is ~500 MB/s of leaked VRAM.
surface()— itassert_eq!s on NV12 YUV and panics on BGRA.2. The
Systemtheme does not exist in the kit.ThemeModehas onlyLight | Dark,theme::initforcesLight, and nothing observes the OSappearance. It is built here, and the preference is persisted next to the
recents in
~/.config/rustmotion/.3. Percent-coordinate overlays work natively —
.absolute().left(relative(0.25))— so the hit-test overlay maps overdirectly.
Workstreams
Root+ the 3 overlay layers, light/dark/system theme, persistedapp/root.rs,app/window.rs,theme/; deletescomponents/**+assets/RenderImage,drop_imagediscipline,request_animation_frameloop, prefetch publishereditor/surface.rs, additive inframes.rslibrary/view.rseditor/view.rs,topbar.rs,overlay.rs, view part ofplayback.rseditor/inspector/**diff_panel.rs,annotations.rs, toast ofexport.rsOrchestrator-owned convergence points — no workstream writes these:
Cargo.toml,lib.rs, the fourmod.rs,.github/workflows/ci.yamlandpublish.yaml(dropping GTK/WebKit/xdo),CLAUDE.md.Regenerated at integration, never hand-edited:
Cargo.lock, viacargo check --workspace.Schedule — pipeline, not barrier:
Each agent gets its own
CARGO_TARGET_DIR; otherwise cargo serializes thebuilds however well the sources are partitioned.
Known risks, and how they are contained
Root::render_dialog_layer/_sheet_layer/_notification_layerareremoved in 0.7.0, with no documented replacement — and they are exactly what
WS-1 and WS-6 need. Contained by pinning
gpui-kit = "=0.6.6"and isolatingthose three calls in a single file, so the 0.7 migration is a localised patch
rather than a hunt.
The
=0.3.6pin ongpui-preis viral. Every kit crate pins it exactly,so a version bump moves the whole graph in lockstep, with no overlap window.
Breaking changes land in patch slots — 0.6.2 removed the dock tiles canvas.
These are 0.x crates with no declared semver policy and no MSRV.
Accepted deliberately: gpui-kit depends on
gpui-pre, a third-party snapshotof Zed's gpui at commit
bcf6582published by the kit's maintainer, not onZed's official
gpui. There is no alternative — officialgpuiis 0.2.2 andhas been frozen since 2025-10-22.
gpui-component, the substance of the kit,is mature: first published 2025-02, 30 releases, 133k downloads. It is the
gpui-kitumbrella that is young (crate name created 2026-09-03).Side benefit
Dioxus desktop is the only reason
ci.yamlandpublish.yamlinstalllibwebkit2gtk-4.1-dev,libgtk-3-devandlibxdo-dev. Those go away.