diff --git a/src/renderer/src/components/editor/EditorPanelHeader.tsx b/src/renderer/src/components/editor/EditorPanelHeader.tsx index f47c9190c..23ce65305 100644 --- a/src/renderer/src/components/editor/EditorPanelHeader.tsx +++ b/src/renderer/src/components/editor/EditorPanelHeader.tsx @@ -100,6 +100,31 @@ export function EditorPanelHeader({ onOpenMarkdownPreview={onOpenMarkdownPreview} onOpenContainingFolder={onOpenContainingFolder} /> + {canOpenPreviewToSide && ( + + + + + + + {translate( + 'auto.components.editor.EditorPanelHeader.fb8331694e', + 'Open Preview to the Side' + )} + + + + )} {isSingleDiff && ( @@ -149,31 +174,6 @@ export function EditorPanelHeader({ iconClassName="size-3" /> )} - {canOpenPreviewToSide && ( - - - - - - - {translate( - 'auto.components.editor.EditorPanelHeader.fb8331694e', - 'Open Preview to the Side' - )} - - - - )} {isDiffSurface && ( diff --git a/src/renderer/src/components/editor/editor-panel-render-model.test.ts b/src/renderer/src/components/editor/editor-panel-render-model.test.ts index 374e870e4..88e3a539e 100644 --- a/src/renderer/src/components/editor/editor-panel-render-model.test.ts +++ b/src/renderer/src/components/editor/editor-panel-render-model.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest' import type { OpenFile } from '@/store/slices/editor' import { RICH_MARKDOWN_MAX_SIZE_BYTES } from '../../../../shared/constants' +import type { GitStatusEntry } from '../../../../shared/types' import type { FileContent } from './editor-panel-content-types' import { getEditorPanelRenderModel } from './editor-panel-render-model' @@ -31,18 +32,82 @@ function renderModel(args: { editorDrafts?: Record markdownViewMode?: Record isChangesMode?: boolean + gitStatusByWorktree?: Record }) { return getEditorPanelRenderModel({ activeFile: args.activeFile ?? markdownFile(), fileContents: args.fileContents ?? { '/repo/README.md': textContent() }, editorDrafts: args.editorDrafts ?? {}, - gitStatusByWorktree: {}, + gitStatusByWorktree: args.gitStatusByWorktree ?? {}, gitBranchChangesByWorktree: {}, markdownViewMode: args.markdownViewMode ?? {}, isChangesMode: args.isChangesMode ?? false }) } +function htmlFile(overrides: Partial = {}): OpenFile { + return { + id: '/repo/mock.html', + filePath: '/repo/mock.html', + relativePath: 'mock.html', + worktreeId: 'wt-1', + language: 'html', + mode: 'edit', + isDirty: false, + ...overrides + } +} + +describe('getEditorPanelRenderModel HTML preview affordance', () => { + it('enables preview for HTML edit tabs', () => { + expect(renderModel({ activeFile: htmlFile(), fileContents: {} }).canOpenPreviewToSide).toBe( + true + ) + }) + + it('enables preview for single HTML diffs whose file exists on disk', () => { + const model = renderModel({ + activeFile: htmlFile({ mode: 'diff', diffSource: 'unstaged' } as Partial), + fileContents: {}, + gitStatusByWorktree: { + 'wt-1': [{ path: 'mock.html', status: 'modified', area: 'unstaged' }] + } + }) + + expect(model.canOpenPreviewToSide).toBe(true) + }) + + it('disables preview for diffs of deleted HTML files', () => { + const model = renderModel({ + activeFile: htmlFile({ mode: 'diff', diffSource: 'unstaged' } as Partial), + fileContents: {}, + gitStatusByWorktree: { + 'wt-1': [{ path: 'mock.html', status: 'deleted', area: 'unstaged' }] + } + }) + + expect(model.canOpenPreviewToSide).toBe(false) + }) + + it('disables preview for commit diffs whose content may not match disk', () => { + const model = renderModel({ + activeFile: htmlFile({ mode: 'diff', diffSource: 'commit' } as Partial), + fileContents: {} + }) + + expect(model.canOpenPreviewToSide).toBe(false) + }) + + it('disables preview for non-HTML diffs', () => { + const model = renderModel({ + activeFile: markdownFile({ mode: 'diff', diffSource: 'unstaged' } as Partial), + fileContents: {} + }) + + expect(model.canOpenPreviewToSide).toBe(false) + }) +}) + describe('getEditorPanelRenderModel markdown export affordance', () => { it('enables export for rendered markdown edit tabs', () => { expect(renderModel({}).canExportMarkdownToPdf).toBe(true) diff --git a/src/renderer/src/components/editor/editor-panel-render-model.ts b/src/renderer/src/components/editor/editor-panel-render-model.ts index be9738db6..c471b2e73 100644 --- a/src/renderer/src/components/editor/editor-panel-render-model.ts +++ b/src/renderer/src/components/editor/editor-panel-render-model.ts @@ -71,6 +71,11 @@ export function getEditorPanelRenderModel({ activeFile.mode === 'diff' && activeFile.diffSource === 'branch' ? (branchEntries.find((entry) => entry.path === activeFile.relativePath) ?? null) : null + const openFileState = getEditorHeaderOpenFileState( + activeFile, + matchingWorktreeEntry, + matchingBranchEntry + ) const markdownViewModes = getMarkdownViewModes({ language: resolvedLanguage, mode: activeFile.mode, @@ -141,16 +146,17 @@ export function getEditorPanelRenderModel({ isCombinedDiff, worktreeEntries, resolvedLanguage, - openFileState: getEditorHeaderOpenFileState( - activeFile, - matchingWorktreeEntry, - matchingBranchEntry - ), + openFileState, isMarkdown: resolvedLanguage === 'markdown', isMermaid: resolvedLanguage === 'mermaid', isCsv: resolvedLanguage === 'csv' || resolvedLanguage === 'tsv', isNotebook: resolvedLanguage === 'notebook', - canOpenPreviewToSide: activeFile.mode === 'edit' && canPreviewLanguage(resolvedLanguage), + // Why: the preview renders the on-disk file, so diff surfaces only get it + // when the modified side still exists on disk (canOpen excludes deleted + // files and commit diffs whose content may not match the working tree). + canOpenPreviewToSide: + canPreviewLanguage(resolvedLanguage) && + (activeFile.mode === 'edit' || (isSingleDiff && openFileState.canOpen)), mdViewMode, hasViewModeToggle, availableEditorToggleModes,