gpui-ai

Readable output

Code diff

A unified patch with line gutters, change tints, per-hunk accept or reject, and a copyable source.

Code diff — gpui-ai

Starts when it scrolls into view

Pop out

NORD FROST · DARK · RADIUS 6/10 · BASE 16PX · SHADOW

Running the real component, compiled to WebAssembly. The frame is 477 px tall, which is what this story measures at the demo width in the default type size — a narrower column or a theme that changes that size will wrap it differently.
Story
code-diff
Demo height
477 px
Events
CodeDiffEvent

Code

Cut from the gallery story this page runs, so it stays true.

crates/gallery/src/gallery.rsOpen in the galleryImplementation
let tokens = cx.theme().semantic_tokens();
let mut file = DiffFile::from_unified(DEMO_PATCH).remove(0);
let hunks: Vec<DiffHunk> = file
    .hunk_refs()
    .iter()
    .cloned()
    .enumerate()
    .map(|(index, hunk)| {
        hunk.review(
            self.hunk_reviews
                .get(&index)
                .copied()
                .unwrap_or_default(),
        )
    })
    .collect();
file = file.hunks(hunks);
let status: SharedString = self
    .last_diff_event
    .clone()
    .unwrap_or_else(|| "Accept or reject a hunk, or collapse the file".into());
v_flex()
    .gap(tokens.spacing.md)
    .child(
        CodeDiff::new("proposed-patch", &file)
            .open(self.diff_open)
            .reviewable(true)
            .on_event(cx.listener(|this, event: &CodeDiffEvent, _, cx| {
                match event {
                    CodeDiffEvent::Toggled { .. } => {
                        this.diff_open = !this.diff_open;
                    }
                    CodeDiffEvent::HunkAccepted { hunk, .. } => {
                        this.hunk_reviews.insert(*hunk, HunkReview::Accepted);
                    }
                    CodeDiffEvent::HunkRejected { hunk, .. } => {
                        this.hunk_reviews.insert(*hunk, HunkReview::Rejected);
                    }
                }
                this.last_diff_event = Some(format!("{event:?}").into());
                cx.notify();
            })),
    )
    .child(
        div()
            .id("code-diff-story-status")
            .role(Role::Status)
            .aria_label(status.clone())
            .text_xs()
            .text_color(cx.theme().muted_foreground)
            .child(status),
    )

Events and ownership

Who owns the state
CodeDiff renders state supplied by the application; it does not own durable work.
What it emits
Interactive intent is reported through the typed CodeDiffEvent contract and stable application IDs.
What it means
A unified patch with line gutters, change tints, per-hunk accept or reject, and a copyable source.
Overflow and motion
Growing content remains reachable in a bounded vertical surface; reduced motion preserves a useful state.

Event types

  • CodeDiffEvent

What the demo does and does not show

The demo above is the same Rust as the native component, compiled to WebAssembly and drawn on a WebGPU canvas — not a mock-up, a recording, or a re-implementation. What it paints is what the component paints: layout, colour from the live theme, and motion.

The live browser specimen requires WebGPU; the native component remains the authoritative runtime.

A canvas cannot stand in for the platform, so treat everything that goes through the operating system as unproven here: keyboard handling and shortcuts, screen reader semantics, text input and IME, the clipboard, file drops, and the frame rate you would get on your own GPU. Judge those in a native build. If your browser has no WebGPU, the frame says so rather than showing an empty box.