@@ -160,6 +160,10 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
160160 const setHoveredId = useViewer ( ( s ) => s . setHoveredId )
161161 const setSelection = useViewer ( ( s ) => s . setSelection )
162162 const nodes = useScene ( ( s ) => s . nodes )
163+ // Read-only scene (e.g. version-preview): suppress the interactive 2D edit
164+ // handles in the overlay pass so a locked plan shows no move/resize/vertex
165+ // affordances. Selection (base-pass hit-lines) and labels still render.
166+ const readOnly = useScene ( ( s ) => s . readOnly )
163167 // When a building is being moved, its explicit selection may be
164168 // cleared as part of the move handoff. Fall back to the
165169 // mid-drag building id so the dimmed floor keeps rendering
@@ -906,9 +910,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() {
906910 still routes through the same selection-handling `<g>` so a
907911 click on a zone's name selects the zone. */ }
908912 < g className = "floorplan-registry-overlay" >
909- { entries . map ( ( { id, overlay } ) =>
910- overlay ? renderEntry ( id , overlay , `overlay-${ id } ` ) : null ,
911- ) }
913+ { entries . map ( ( { id, overlay } ) => {
914+ if ( ! overlay ) return null
915+ // In read-only mode strip the interactive edit handles (keeping
916+ // labels / dimensions) so locked nodes expose no 2D affordances.
917+ const o = readOnly ? stripEditHandleGeometry ( overlay ) : overlay
918+ return o ? renderEntry ( id , o , `overlay-${ id } ` ) : null
919+ } ) }
912920 </ g >
913921 { /* Transient live-rotation readout — drawn last so the wedge + degree
914922 chip sit above all handle chrome while a rotate-arrow is dragged. */ }
@@ -1790,6 +1798,37 @@ const OVERLAY_KINDS = new Set<FloorplanGeometry['kind']>([
17901798 'dimension-label' ,
17911799] )
17921800
1801+ /**
1802+ * Interactive edit-handle kinds — the move/resize/vertex/rotate affordances.
1803+ * A subset of `OVERLAY_KINDS` that excludes the informational `text` /
1804+ * `dimension` / `dimension-label`, so a read-only scene can hide the handles
1805+ * while keeping measurements and labels visible.
1806+ */
1807+ const EDIT_HANDLE_KINDS = new Set < FloorplanGeometry [ 'kind' ] > ( [
1808+ 'endpoint-handle' ,
1809+ 'midpoint-handle' ,
1810+ 'edge-handle' ,
1811+ 'move-handle' ,
1812+ 'move-arrow' ,
1813+ 'rotate-arrow' ,
1814+ ] )
1815+
1816+ /**
1817+ * Remove interactive edit-handle geometry from an overlay tree, preserving all
1818+ * other overlay primitives (labels, dimensions). Returns `null` if nothing
1819+ * remains.
1820+ */
1821+ function stripEditHandleGeometry ( g : FloorplanGeometry ) : FloorplanGeometry | null {
1822+ if ( EDIT_HANDLE_KINDS . has ( g . kind ) ) return null
1823+ if ( g . kind === 'group' ) {
1824+ const children = g . children
1825+ . map ( stripEditHandleGeometry )
1826+ . filter ( ( child ) : child is FloorplanGeometry => child !== null )
1827+ return children . length > 0 ? { ...g , children } : null
1828+ }
1829+ return g
1830+ }
1831+
17931832/**
17941833 * Walk a `FloorplanGeometry` tree and split it into two trees: one with
17951834 * only "base" primitives (polygons, paths, fills, strokes) and one with
0 commit comments