Skip to content

chantier: rewrite the studio UI onto gpui-kit so it can be published #306

Description

@LeadcodeDev

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions