diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd69f369d3903161dc701177b68582e0c78db685..1ce156f5543e9b1e57a6ed05ff7aee1ae3d1333e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ n
 - Add various options to customize the axes camera-helper
 - Fix issue with texture-mesh color smoothing when changing themes
 - Add fast boundary helper and corresponding unit trait
+- Add Observable for Canvas3D commits
 
 ## [v3.30.0] - 2023-01-29
 
diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index b65f42eb74df809aabf86620e4d5cbd07f911673..aeb8d12ceac26f586dd281654de73eb64efceb38 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -260,6 +260,7 @@ interface Canvas3D {
     notifyDidDraw: boolean,
     readonly didDraw: BehaviorSubject<now.Timestamp>
     readonly commited: BehaviorSubject<now.Timestamp>
+    readonly commitQueueSize: BehaviorSubject<number>
     readonly reprCount: BehaviorSubject<number>
     readonly resized: BehaviorSubject<any>
 
@@ -306,6 +307,7 @@ namespace Canvas3D {
         let startTime = now();
         const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
         const commited = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
+        const commitQueueSize = new BehaviorSubject<number>(0);
 
         const { gl, contextRestored } = webgl;
 
@@ -593,7 +595,11 @@ namespace Canvas3D {
             // snapshot the current bounding sphere of visible objects
             Sphere3D.copy(oldBoundingSphereVisible, scene.boundingSphereVisible);
 
-            if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) return false;
+            if (!scene.commit(isSynchronous ? void 0 : sceneCommitTimeoutMs)) {
+                commitQueueSize.next(scene.commitQueueSize);
+                return false;
+            }
+            commitQueueSize.next(0);
 
             if (helper.debug.isEnabled) helper.debug.update();
             if (!p.camera.manualReset && (reprCount.value === 0 || shouldResetCamera())) {
@@ -790,6 +796,7 @@ namespace Canvas3D {
             set notifyDidDraw(v: boolean) { notifyDidDraw = v; },
             didDraw,
             commited,
+            commitQueueSize,
             reprCount,
             resized,
             setProps: (properties, doNotRequestDraw = false) => {
diff --git a/src/mol-gl/commit-queue.ts b/src/mol-gl/commit-queue.ts
index c0355e3cc322d68f4813bd64a9b458a40ffbf8e7..c4f6e3824b7c984dcf9b7fe929dfaf774ef6089e 100644
--- a/src/mol-gl/commit-queue.ts
+++ b/src/mol-gl/commit-queue.ts
@@ -19,6 +19,10 @@ export class CommitQueue {
         return this.removeList.count === 0 && this.addList.count === 0;
     }
 
+    get size() {
+        return this.removeMap.size + this.addMap.size;
+    }
+
     add(o: GraphicsRenderObject) {
         if (this.removeMap.has(o)) {
             const a = this.removeMap.get(o)!;
diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts
index a8d32ab433578eadd79980ce165fb49dc9864cfe..2f84b7541b13697eca009b64ae3f34500583f018 100644
--- a/src/mol-gl/scene.ts
+++ b/src/mol-gl/scene.ts
@@ -77,6 +77,7 @@ interface Scene extends Object3D {
     remove: (o: GraphicsRenderObject) => void
     commit: (maxTimeMs?: number) => boolean
     readonly needsCommit: boolean
+    readonly commitQueueSize: number
     has: (o: GraphicsRenderObject) => boolean
     clear: () => void
     forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
@@ -279,6 +280,7 @@ namespace Scene {
             add: (o: GraphicsRenderObject) => commitQueue.add(o),
             remove: (o: GraphicsRenderObject) => commitQueue.remove(o),
             commit: (maxTime = Number.MAX_VALUE) => commit(maxTime),
+            get commitQueueSize() { return commitQueue.size; },
             get needsCommit() { return !commitQueue.isEmpty; },
             has: (o: GraphicsRenderObject) => {
                 return renderableMap.has(o);
diff --git a/src/mol-plugin-ui/task.tsx b/src/mol-plugin-ui/task.tsx
index f0520f683fd552fb4d75fe51ac194ee585ca662f..c262ad8db54519bbda309e66efe8754b4a718af8 100644
--- a/src/mol-plugin-ui/task.tsx
+++ b/src/mol-plugin-ui/task.tsx
@@ -11,6 +11,7 @@ import { Progress } from '../mol-task';
 import { IconButton } from './controls/common';
 import { CancelSvg } from './controls/icons';
 import { useContext, useEffect, useState } from 'react';
+import { useBehavior } from './hooks/use-behavior';
 
 export function BackgroundTaskProgress() {
     const plugin = useContext(PluginReactContext);
@@ -36,6 +37,22 @@ export function BackgroundTaskProgress() {
 
     return <div className='msp-background-tasks'>
         {tracked.valueSeq().map(e => <ProgressEntry key={e!.id} event={e!} />)}
+        <CanvasCommitState />
+    </div>;
+}
+
+function CanvasCommitState() {
+    const plugin = useContext(PluginReactContext);
+    const queueSize = useBehavior(plugin.canvas3d?.commitQueueSize);
+
+    if (!queueSize) return null;
+
+    return <div className='msp-task-state'>
+        <div>
+            <div>
+                Commiting renderables... {queueSize} remaining
+            </div>
+        </div>
     </div>;
 }