diff --git a/frontend/src/pages/Editor.tsx b/frontend/src/pages/Editor.tsx index 603f0ef..df51992 100644 --- a/frontend/src/pages/Editor.tsx +++ b/frontend/src/pages/Editor.tsx @@ -28,6 +28,8 @@ import Minimap from '../components/Minimap'; import UploadPanel from '../components/UploadPanel'; import ExportDialog from '../components/ExportDialog'; import FeedbackPanel from '../components/feedback/FeedbackPanel'; +import InlineCommentComposer from '../components/feedback/InlineCommentComposer'; +import { useAnchoredOverlayPosition } from '../hooks/useAnchoredOverlayPosition'; import { UploadManager } from '../stores/uploadManager'; import { InboxZone } from '../canvas/InboxZone'; import { getItemWorldBounds } from '../canvas/SceneManager'; @@ -111,6 +113,7 @@ export default function Editor({ isPublicView }: EditorProps) { const expandSeqRef = useRef(0); const [draftCommentText, setDraftCommentText] = useState(''); const [creatingPointThread, setCreatingPointThread] = useState(false); + const [sceneVersion, setSceneVersion] = useState(0); const [focusMode, setFocusMode] = useState(false); const [layerList, setLayerList] = useState([]); const [selectedLayerIds, setSelectedLayerIds] = useState([]); @@ -150,6 +153,7 @@ export default function Editor({ isPublicView }: EditorProps) { // Omit for structural changes — falls through to debounced full scene sync. const onCanvasChange = useCallback((changedIds?: string[]) => { scheduleSave(); + setSceneVersion(v => v + 1); if (changedIds && changedIds.length > 0) { // Incremental: broadcast only changed elements syncRef.current?.broadcastElements(changedIds); @@ -204,6 +208,15 @@ export default function Editor({ isPublicView }: EditorProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [objectCount]); + // Inline composer position — tracks viewport + scene changes for anchored placement + const composerAnchor = draftPin ? { objectId: draftPin.objectId, pinX: draftPin.pinX, pinY: draftPin.pinY } : null; + const composerPos = useAnchoredOverlayPosition( + canvasRef.current?.getViewport() ?? null, + canvasRef.current?.getScene() ?? null, + composerAnchor, + sceneVersion, + ); + // Keep reviewModeRef in sync for tool handlers to read useEffect(() => { reviewModeRef.current = reviewMode; @@ -960,6 +973,32 @@ export default function Editor({ isPublicView }: EditorProps) { )} + {/* Inline comment composer — anchored to draft pin */} + {reviewMode && draftPin && ( + { + if (!draftPin || !draftCommentText.trim()) return; + await handleCreatePointThread(draftPin, draftCommentText.trim()); + }} + onCancel={() => { + setDraftPin(null); + setDraftCommentText(''); + }} + submitting={creatingPointThread} + /> + )} + {/* Context menu */} {contextMenu && (