11import { useT } from '@open-codesign/i18n' ;
22import { buildPreviewDocument , isRenderablePath } from '@open-codesign/runtime' ;
3- import { DEFAULT_SOURCE_ENTRY , LEGACY_SOURCE_ENTRY , type PreviewMode } from '@open-codesign/shared' ;
3+ import {
4+ type CommentRow ,
5+ DEFAULT_SOURCE_ENTRY ,
6+ LEGACY_SOURCE_ENTRY ,
7+ type PreviewMode ,
8+ } from '@open-codesign/shared' ;
49import {
510 ChevronRight ,
611 ExternalLink ,
@@ -41,7 +46,9 @@ import {
4146 handlePreviewMessage ,
4247 isTrustedPreviewMessageSource ,
4348 type PreviewMessageHandlers ,
49+ postClearPinToPreviewWindow ,
4450 postModeToPreviewWindow ,
51+ postPinSelectorToPreviewWindow ,
4552 scaleRectForZoom ,
4653 stablePreviewSourceKey ,
4754} from '../preview/helpers' ;
@@ -783,6 +790,12 @@ export function shouldShowTweakPanelForFile(input: {
783790 ) ;
784791}
785792
793+ export function shouldEnableWorkspaceFilePreviewInteractions ( input : {
794+ previewKind : FilePreviewKind | null ;
795+ } ) : boolean {
796+ return input . previewKind === 'runtime' ;
797+ }
798+
786799export function shouldUseDesignPreviewResolverForFile ( input : {
787800 path : string ;
788801 previewKind : FilePreviewKind ;
@@ -924,14 +937,40 @@ interface WorkspaceFilePreviewProps {
924937
925938interface WorkspaceFilePreviewMessageHandlerInput {
926939 previewZoom : number ;
940+ comments ?: CommentRow [ ] | undefined ;
941+ currentSnapshotId ?: string | null | undefined ;
927942 selectCanvasElement : ReturnType < typeof useCodesignStore . getState > [ 'selectCanvasElement' ] ;
928943 openCommentBubble : ReturnType < typeof useCodesignStore . getState > [ 'openCommentBubble' ] ;
929944 applyLiveRects : ReturnType < typeof useCodesignStore . getState > [ 'applyLiveRects' ] ;
930945 pushIframeError : ReturnType < typeof useCodesignStore . getState > [ 'pushIframeError' ] ;
931946}
932947
948+ export function findReusableWorkspaceFileCommentForSelector ( input : {
949+ comments : CommentRow [ ] ;
950+ currentSnapshotId : string | null ;
951+ selector : string ;
952+ } ) : CommentRow | null {
953+ let fallback : CommentRow | null = null ;
954+ for ( let index = input . comments . length - 1 ; index >= 0 ; index -- ) {
955+ const comment = input . comments [ index ] ;
956+ if (
957+ comment ?. kind === 'edit' &&
958+ comment . status === 'pending' &&
959+ comment . selector === input . selector
960+ ) {
961+ if ( input . currentSnapshotId !== null && comment . snapshotId === input . currentSnapshotId ) {
962+ return comment ;
963+ }
964+ fallback ??= comment ;
965+ }
966+ }
967+ return fallback ;
968+ }
969+
933970export function createWorkspaceFilePreviewMessageHandlers ( {
934971 previewZoom,
972+ comments = [ ] ,
973+ currentSnapshotId = null ,
935974 selectCanvasElement,
936975 openCommentBubble,
937976 applyLiveRects,
@@ -946,11 +985,19 @@ export function createWorkspaceFilePreviewMessageHandlers({
946985 outerHTML : msg . outerHTML ,
947986 rect : scaled ,
948987 } ) ;
988+ const existingComment = findReusableWorkspaceFileCommentForSelector ( {
989+ comments,
990+ currentSnapshotId,
991+ selector : msg . selector ,
992+ } ) ;
949993 openCommentBubble ( {
950994 selector : msg . selector ,
951995 tag : msg . tag ,
952996 outerHTML : msg . outerHTML ,
953997 rect : scaled ,
998+ ...( existingComment
999+ ? { existingCommentId : existingComment . id , initialText : existingComment . text }
1000+ : { } ) ,
9541001 ...( typeof msg . parentOuterHTML === 'string' && msg . parentOuterHTML . length > 0
9551002 ? { parentOuterHTML : msg . parentOuterHTML }
9561003 : { } ) ,
@@ -1431,6 +1478,9 @@ export function WorkspaceFilePreview({
14311478 const selectCanvasElement = useCodesignStore ( ( s ) => s . selectCanvasElement ) ;
14321479 const openCommentBubble = useCodesignStore ( ( s ) => s . openCommentBubble ) ;
14331480 const applyLiveRects = useCodesignStore ( ( s ) => s . applyLiveRects ) ;
1481+ const comments = useCodesignStore ( ( s ) => s . comments ) ;
1482+ const currentSnapshotId = useCodesignStore ( ( s ) => s . currentSnapshotId ) ;
1483+ const commentBubble = useCodesignStore ( ( s ) => s . commentBubble ) ;
14341484 const { files : observedFiles } = useDesignFiles ( files ? null : currentDesignId ) ;
14351485 const workspaceFiles = files ?? observedFiles ;
14361486 const currentDesign = designs . find ( ( d ) => d . id === currentDesignId ) ;
@@ -1481,6 +1531,8 @@ export function WorkspaceFilePreview({
14811531 event . data ,
14821532 createWorkspaceFilePreviewMessageHandlers ( {
14831533 previewZoom,
1534+ comments,
1535+ currentSnapshotId,
14841536 selectCanvasElement : ( selection ) => {
14851537 if ( interactive ) selectCanvasElement ( selection ) ;
14861538 } ,
@@ -1500,6 +1552,8 @@ export function WorkspaceFilePreview({
15001552 } , [
15011553 pushIframeError ,
15021554 previewZoom ,
1555+ comments ,
1556+ currentSnapshotId ,
15031557 selectCanvasElement ,
15041558 openCommentBubble ,
15051559 applyLiveRects ,
@@ -1514,6 +1568,19 @@ export function WorkspaceFilePreview({
15141568 ) ;
15151569 } , [ interactionMode , pushIframeError , interactive ] ) ;
15161570
1571+ useEffect ( ( ) => {
1572+ if ( ! interactive ) return ;
1573+ if ( commentBubble && interactionMode === 'comment' ) {
1574+ postPinSelectorToPreviewWindow (
1575+ iframeRef . current ?. contentWindow ,
1576+ commentBubble . selector ,
1577+ pushIframeError ,
1578+ ) ;
1579+ return ;
1580+ }
1581+ postClearPinToPreviewWindow ( iframeRef . current ?. contentWindow , pushIframeError ) ;
1582+ } , [ commentBubble , interactionMode , interactive , pushIframeError ] ) ;
1583+
15171584 useEffect ( ( ) => {
15181585 // Re-read when the file watcher reports changed metadata for either the
15191586 // selected file or an HTML placeholder's resolved JSX/TSX source.
@@ -1728,6 +1795,9 @@ export function FilesTabView({ activePath = null }: { activePath?: string | null
17281795 const externalFallbackFile = externalFallbackPath
17291796 ? ( files . find ( ( f ) => f . path === externalFallbackPath ) ?? null )
17301797 : null ;
1798+ const externalFallbackPreviewKind = externalFallbackPath
1799+ ? previewKindForFile ( externalFallbackPath , externalFallbackFile ?. kind )
1800+ : null ;
17311801 const usesExternalPreview =
17321802 effectivePreviewMode === 'connected-url' || effectivePreviewMode === 'external-app' ;
17331803 const showPreviewHeaderAction =
@@ -1812,7 +1882,9 @@ export function FilesTabView({ activePath = null }: { activePath?: string | null
18121882 path = { externalFallbackPath }
18131883 file = { externalFallbackFile }
18141884 files = { files }
1815- interactive = { isDedicatedFileTab }
1885+ interactive = { shouldEnableWorkspaceFilePreviewInteractions ( {
1886+ previewKind : externalFallbackPreviewKind ,
1887+ } ) }
18161888 />
18171889 ) ;
18181890 }
@@ -1829,7 +1901,9 @@ export function FilesTabView({ activePath = null }: { activePath?: string | null
18291901 path = { selectedPath }
18301902 file = { selectedFile }
18311903 files = { files }
1832- interactive = { isDedicatedFileTab }
1904+ interactive = { shouldEnableWorkspaceFilePreviewInteractions ( {
1905+ previewKind : previewKindForFile ( selectedPath , selectedFile . kind ) ,
1906+ } ) }
18331907 />
18341908 ) ;
18351909 }
@@ -1841,7 +1915,9 @@ export function FilesTabView({ activePath = null }: { activePath?: string | null
18411915 path = { selectedPath }
18421916 file = { selectedFile }
18431917 files = { files }
1844- interactive = { isDedicatedFileTab }
1918+ interactive = { shouldEnableWorkspaceFilePreviewInteractions ( {
1919+ previewKind : previewKindForFile ( selectedPath , selectedFile ?. kind ) ,
1920+ } ) }
18451921 />
18461922 ) ;
18471923 }
0 commit comments