diff --git a/packages/editor/src/components/editor/snapshot-capture-overlay.tsx b/packages/editor/src/components/editor/snapshot-capture-overlay.tsx
index c8710c03..9b6d0068 100644
--- a/packages/editor/src/components/editor/snapshot-capture-overlay.tsx
+++ b/packages/editor/src/components/editor/snapshot-capture-overlay.tsx
@@ -497,8 +497,9 @@ export function SnapshotCaptureOverlay({ projectId }: { projectId: string }) {
)}
- {/* Top-center HUD — what the shot will be */}
- {!isMobile && (
+ {/* Top-center HUD — what the shot will be. Preset captures are a fixed
+ square and carry their own "Frame your item" banner up there. */}
+ {!isMobile && !isPreset && (
)}
- {!isMobile && (
+ {/* Preset captures carry their own "Frame your item" banner — the
+ snapshot pitch only applies to the studio/reference flow. */}
+ {!isMobile && !isPreset && (
A snapshot
{' freezes this exact camera angle as a reusable reference for renders & videos.'}
@@ -608,7 +611,7 @@ export function SnapshotCaptureOverlay({ projectId }: { projectId: string }) {
)}
diff --git a/packages/editor/src/components/ui/helpers/helper-manager.tsx b/packages/editor/src/components/ui/helpers/helper-manager.tsx
index af21feab..f3bacf0c 100644
--- a/packages/editor/src/components/ui/helpers/helper-manager.tsx
+++ b/packages/editor/src/components/ui/helpers/helper-manager.tsx
@@ -95,6 +95,7 @@ function useActiveModifierKeys(): ActiveModifierKeys {
export function HelperManager() {
const mode = useEditor((s) => s.mode)
const tool = useEditor((s) => s.tool)
+ const workspaceMode = useEditor((s) => s.workspaceMode)
const scope = useInteractionScope((s) => s.scope)
const movingNode = useMovingNode()
const activeHandleDrag = useActiveHandleDrag()
@@ -146,6 +147,10 @@ export function HelperManager() {
// Helpers are keyboard-driven hints (Esc, R, etc.) — irrelevant on touch.
if (isMobile) return null
+ // The studio workspace (compose panel / gallery) has no scene selection or
+ // tools — editor shortcut hints would only mislead there.
+ if (workspaceMode === 'studio') return null
+
// Rotating a node (or a multi-selection group) via its in-world gizmo:
// advertise Shift = free rotation, the same angle-step bypass wall drafting
// exposes. Takes priority over the idle select-mode hints since a handle
diff --git a/packages/viewer/src/components/viewer/frame-limiter.tsx b/packages/viewer/src/components/viewer/frame-limiter.tsx
index 962dd826..11155d41 100644
--- a/packages/viewer/src/components/viewer/frame-limiter.tsx
+++ b/packages/viewer/src/components/viewer/frame-limiter.tsx
@@ -1,5 +1,6 @@
import { useThree } from '@react-three/fiber'
import { useLayoutEffect } from 'react'
+import useViewer from '../../store/use-viewer'
type FrameLimiterProps = {
fps?: number
@@ -8,8 +9,11 @@ type FrameLimiterProps = {
const FrameLimiter: React.FC = ({ fps = 50 }) => {
const { advance, set, frameloop: initFrameloop, scene, clock } = useThree()
const renderer = useThree((state) => state.gl)
+ // Fully covered canvas (e.g. studio gallery) → stop advancing frames
+ const renderPaused = useViewer((s) => s.renderPaused)
useLayoutEffect(() => {
+ if (renderPaused) return
let elapsed = 0
let then = 0
let i = 0
@@ -35,7 +39,7 @@ const FrameLimiter: React.FC = ({ fps = 50 }) => {
}
set({ frameloop: initFrameloop })
}
- }, [fps, advance, set, initFrameloop])
+ }, [fps, advance, set, initFrameloop, renderPaused])
return null
}
diff --git a/packages/viewer/src/store/use-viewer.ts b/packages/viewer/src/store/use-viewer.ts
index ade93265..a2bd4ba6 100644
--- a/packages/viewer/src/store/use-viewer.ts
+++ b/packages/viewer/src/store/use-viewer.ts
@@ -49,6 +49,10 @@ type ViewerState = {
isExporting: boolean
setExporting: (value: boolean) => void
+ /** Suspend the render loop while the canvas is fully covered (e.g. studio gallery). */
+ renderPaused: boolean
+ setRenderPaused: (value: boolean) => void
+
shading: RenderShading
shadingByContext: Partial>
setShading: (shading: RenderShading) => void
@@ -233,6 +237,9 @@ const useViewer = create()(
isExporting: false,
setExporting: (value) => set({ isExporting: value }),
+ renderPaused: false,
+ setRenderPaused: (value) => set({ renderPaused: value }),
+
shading: 'rendered',
shadingByContext: {},
setShading: (shading) =>