Skip to content

Image previews#38

Merged
dannysmith merged 10 commits intomainfrom
image-previews
Oct 22, 2025
Merged

Image previews#38
dannysmith merged 10 commits intomainfrom
image-previews

Conversation

@dannysmith
Copy link
Copy Markdown
Owner

@dannysmith dannysmith commented Oct 22, 2025

Shows an image preview when hovering over image URL in the editor. Works with both remote images and local images.

Summary by CodeRabbit

  • New Features

    • Added image preview functionality: hold Alt and hover over image references in the editor to preview them instantly. Supports local files (absolute and relative paths) and remote URLs across Markdown, HTML, and plain text.
  • Documentation

    • Added comprehensive implementation guide detailing image preview architecture, path resolution, and integration flow.
    • Added implementation task documentation for supporting images in frontmatter.
    • Added test article with diverse image reference scenarios for validation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 22, 2025

Walkthrough

Implements end-to-end Image Preview feature with backend path resolution (Tauri command), frontend hover detection (Alt-key state tracking), image preview component rendering, and supporting utilities for detecting image URLs across Markdown/HTML. Includes configuration updates, test coverage, and documentation.

Changes

Cohort / File(s) Summary
Backend Image Path Resolution
src-tauri/Cargo.toml, src-tauri/src/commands/files.rs, src-tauri/src/lib.rs, src-tauri/tauri.conf.json
Added protocol-asset feature to Tauri dependency; implemented resolve_image_path command to validate and resolve absolute/relative image paths within project root; registered command in invoke handler; configured CSP and asset protocol scope in security settings.
Frontend Components & Hooks
src/components/editor/Editor.tsx, src/components/editor/ImagePreview.tsx, src/hooks/editor/useImageHover.ts, src/hooks/editor/index.ts
Created ImagePreview memoized component with loading/success/error states and file path resolution; implemented useImageHover hook to detect hovered image URLs in editor when Alt-key pressed; integrated both into Editor component with project path and current file tracking; exported new hooks and types.
Image Detection & Utilities
src/lib/editor/urls/detection.ts, src/lib/editor/urls/detection.test.ts
Added isImageUrl() and findImageUrlsAndPathsInText() functions to detect image references (remote, relative, absolute paths) across text; extended test suite with comprehensive coverage for image extension detection, path types, and markdown/HTML formats.
Documentation & Test Content
docs/developer/image-preview-implementation.md, docs/tasks-done/task-1-image-previews.md, docs/tasks-todo/task-2-images-in-frontmatter.md, test/dummy-astro-project/src/content/articles/2025-01-22-image-preview-test.md
Added architectural documentation describing end-to-end implementation flow, path resolution strategies, and security considerations; documented completed task with phases and integration notes; outlined future frontmatter image support task; created test article exercising absolute/relative/remote/HTML image references.

Sequence Diagram

sequenceDiagram
    participant User
    participant Editor
    participant useImageHover
    participant ImagePreview
    participant Tauri
    participant FileSystem
    
    User->>Editor: Hover over image + press Alt
    Editor->>useImageHover: Emit mousemove event with Alt state
    useImageHover->>useImageHover: Convert mouse to doc position
    useImageHover->>useImageHover: Extract image URLs from line
    useImageHover->>Editor: Return HoveredImage
    
    Editor->>ImagePreview: Pass hoveredImage, projectPath, currentFilePath
    
    alt Local Image Path
        ImagePreview->>Tauri: invoke('resolve_image_path', {path, projectRoot, currentFile})
        Tauri->>FileSystem: Validate path within project root
        FileSystem-->>Tauri: Canonical path / error
        Tauri-->>ImagePreview: Resolved absolute path
        ImagePreview->>ImagePreview: Convert to asset URL
    else Remote URL
        ImagePreview->>ImagePreview: Use URL directly
    end
    
    ImagePreview->>ImagePreview: Render preview or loading state
    ImagePreview-->>User: Display image preview
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

The changes span frontend components, backend commands, detection utilities, and configuration with mixed complexity. Logic includes state machines, path validation, and hover tracking across multiple files. Consistent patterns (image detection, state management) reduce friction, but multiple interdependencies and new public APIs require careful integration review.

Poem

🐰 A rabbit hops through images bright,
Alt-key whispers, hovering in sight!
Paths resolved from root to file,
Previews bloom in editor's style—
asset protocol sparkles wide!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Image previews" directly corresponds to the primary feature being introduced across the entire changeset. The PR implements a complete image preview system including backend path resolution via a new Tauri command, frontend React components and hooks for hover detection, utility functions for image URL/path detection, security configuration updates, and comprehensive documentation. The title accurately captures this core functionality in a concise manner and is specific enough for a developer scanning the commit history to understand that image preview functionality is being added.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch image-previews

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (9)
src/lib/editor/urls/detection.test.ts (2)

274-323: Edge-case expectation: isImageUrl('.png') === true — confirm intent.

Accepting bare extensions as “image URLs” may surprise future callers outside the text scanner. If intentional, add a brief docstring note; otherwise, require at least one path segment.


325-504: Great coverage; add a couple more cases for robustness.

Consider tests for:

  • Data URLs: data:image/png;base64,... (should likely be false).
  • File URLs: file:///... (should be false).
  • URLs with fragments: image.png#v=1 (should be true).
docs/tasks-done/task-1-image-previews.md (2)

37-41: Docs/config mismatch: allowlist https: in CSP.

Docs state remote images work; ensure the CSP example includes https: in img-src to match the implementation change suggested in tauri.conf.json.


5-9: Minor copy nits.

Capitalize “Markdown”; consider replacing bolded section titles with proper headings to satisfy MD036.

src/components/editor/ImagePreview.tsx (1)

69-69: Simplify the dependency array.

The dependency array includes both hoveredImage?.url and hoveredImage, which is redundant. Since useImageHover returns the same object reference when the URL hasn't changed (as shown in the implementation), including just hoveredImage is sufficient to track all necessary changes.

Apply this diff to simplify:

-  }, [hoveredImage?.url, projectPath, currentFilePath, hoveredImage])
+  }, [hoveredImage, projectPath, currentFilePath])
src-tauri/src/commands/files.rs (1)

1013-1053: LGTM with a minor documentation suggestion.

The path resolution logic is well-structured and secure:

  • Properly handles absolute, relative, and ambiguous paths
  • Uses validate_project_path to prevent directory traversal attacks
  • Validates file existence before returning
  • Clear error messages

The comment on line 1035 says "try as absolute from project root first" but there's no fallback or "second" option. Consider simplifying to just "treat as absolute from project root" for clarity.

docs/developer/image-preview-implementation.md (2)

5-5: Consider using proper capitalization.

"markdown" should be "Markdown" when referring to the formatting language as a proper noun.


19-38: Consider adding language identifiers to code blocks.

The code blocks at lines 19, 25, and 35 should specify a language identifier for better syntax highlighting in documentation viewers. For example, the paths at lines 20, 26, and 36-37 could use text or bash as the language identifier.

src/hooks/editor/useImageHover.ts (1)

71-74: Consider clarifying the catch block comment.

The catch block is generic and will catch any error, not just "position is out of bounds" errors. While failing gracefully for all errors is reasonable here, consider updating the comment to reflect this broader error handling.

-      } catch {
-        // Silently fail if position is out of bounds
+      } catch {
+        // Silently fail for any errors (e.g., position out of bounds, invalid state)
         setHoveredImage(null)
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b27f8b and 4d6eec0.

⛔ Files ignored due to path filters (2)
  • src-tauri/Cargo.lock is excluded by !**/*.lock
  • test/dummy-astro-project/src/content/articles/imagetest.png is excluded by !**/*.png
📒 Files selected for processing (14)
  • docs/developer/image-preview-implementation.md (1 hunks)
  • docs/tasks-done/task-1-image-previews.md (1 hunks)
  • docs/tasks-todo/task-2-images-in-frontmatter.md (1 hunks)
  • src-tauri/Cargo.toml (1 hunks)
  • src-tauri/src/commands/files.rs (1 hunks)
  • src-tauri/src/lib.rs (1 hunks)
  • src-tauri/tauri.conf.json (1 hunks)
  • src/components/editor/Editor.tsx (4 hunks)
  • src/components/editor/ImagePreview.tsx (1 hunks)
  • src/hooks/editor/index.ts (2 hunks)
  • src/hooks/editor/useImageHover.ts (1 hunks)
  • src/lib/editor/urls/detection.test.ts (2 hunks)
  • src/lib/editor/urls/detection.ts (2 hunks)
  • test/dummy-astro-project/src/content/articles/2025-01-22-image-preview-test.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src-tauri/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Write and maintain Rust tests and code for the Tauri backend with modern Rust formatting (use format("{variable}"))

Files:

  • src-tauri/src/lib.rs
  • src-tauri/src/commands/files.rs
src/lib/**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Write unit tests for src/lib modules as .test.ts files under src/lib

Files:

  • src/lib/editor/urls/detection.test.ts
src/components/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use the Direct Store Pattern in React components (access Zustand stores directly) and never use React Hook Form

Files:

  • src/components/editor/ImagePreview.tsx
  • src/components/editor/Editor.tsx
🧬 Code graph analysis (7)
src/hooks/editor/useImageHover.ts (2)
src/hooks/editor/index.ts (2)
  • HoveredImage (19-19)
  • useImageHover (18-18)
src/lib/editor/urls/detection.ts (1)
  • findImageUrlsAndPathsInText (114-183)
src-tauri/src/lib.rs (1)
src-tauri/src/commands/files.rs (1)
  • resolve_image_path (1013-1053)
src/lib/editor/urls/detection.ts (1)
src/lib/editor/dragdrop/fileProcessing.ts (1)
  • IMAGE_EXTENSIONS (10-19)
src-tauri/src/commands/files.rs (1)
src-tauri/src/commands/mdx_components.rs (1)
  • validate_project_path (17-32)
src/lib/editor/urls/detection.test.ts (1)
src/lib/editor/urls/detection.ts (2)
  • isImageUrl (95-99)
  • findImageUrlsAndPathsInText (114-183)
src/components/editor/ImagePreview.tsx (1)
src/hooks/editor/useImageHover.ts (1)
  • HoveredImage (5-9)
src/components/editor/Editor.tsx (3)
src/store/editorStore.ts (1)
  • useEditorStore (219-490)
src/store/projectStore.ts (1)
  • useProjectStore (42-396)
src/hooks/editor/useImageHover.ts (1)
  • useImageHover (18-116)
🪛 LanguageTool
docs/tasks-todo/task-2-images-in-frontmatter.md

[style] ~9-~9: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...nt this before, it got very complicated very quickly. I don't see why it should be that comp...

(EN_WEAK_ADJECTIVE)

docs/tasks-done/task-1-image-previews.md

[uncategorized] ~7-~7: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...n editor ### Original Requirements In markdown or MDX files when there are image links...

(MARKDOWN_NNP)


[uncategorized] ~7-~7: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...at are inserted using MDX components or markdown links and we need to be able to support...

(MARKDOWN_NNP)


[grammar] ~129-~129: Use a hyphen to join words.
Context: ...or mouse leave - Handles edge cases (out of bounds positions, no view instance) ...

(QB_NEW_EN_HYPHEN)


[grammar] ~129-~129: Use a hyphen to join words.
Context: ...mouse leave - Handles edge cases (out of bounds positions, no view instance) **3...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~173-~173: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...xt`) - ✅ Phase 2: Detects images in markdown, HTML, MDX components, and plain text -...

(MARKDOWN_NNP)

docs/developer/image-preview-implementation.md

[uncategorized] ~5-~5: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...hs/URLs with Alt key held. Works across markdown, HTML, MDX components, and plain text u...

(MARKDOWN_NNP)

🪛 markdownlint-cli2 (0.18.1)
docs/tasks-todo/task-2-images-in-frontmatter.md

5-5: Bare URL used

(MD034, no-bare-urls)

test/dummy-astro-project/src/content/articles/2025-01-22-image-preview-test.md

55-55: Bare URL used

(MD034, no-bare-urls)

docs/tasks-done/task-1-image-previews.md

35-35: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


42-42: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


48-48: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/developer/image-preview-implementation.md

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (15)
src-tauri/Cargo.toml (1)

21-21: Feature alignment looks good.

Adding protocol-asset matches the new asset protocol usage in config and frontend. No issues.

src/lib/editor/urls/detection.test.ts (1)

2-8: Import surface extension is fine.

New exports (isImageUrl, findImageUrlsAndPathsInText) are exercised properly.

src-tauri/src/lib.rs (1)

369-369: LGTM!

The new resolve_image_path command is correctly registered in the invoke handler, following the established pattern for exposing Rust functions to the frontend.

src/hooks/editor/index.ts (1)

9-19: LGTM!

The new exports for the image hover functionality are well-documented and follow the existing module structure.

src/components/editor/Editor.tsx (3)

7-14: LGTM!

The new imports are well-organized and follow the project's module structure.


30-31: LGTM!

Store access follows the Direct Store Pattern correctly, and the image hover hook is properly integrated with the editor's state.

Also applies to: 70-71


251-257: LGTM!

The conditional rendering and prop passing are correct. The || null on line 255 properly converts undefined to null for type compatibility with the ImagePreview component's prop signature.

src/components/editor/ImagePreview.tsx (1)

23-68: LGTM!

The image loading logic is well-structured with proper optimizations:

  • Prevents reloading when hovering over the same URL (lines 32-34)
  • Handles remote and local paths correctly
  • Uses proper async error handling with silent failures
  • Integrates cleanly with Tauri's path resolution and asset protocol
src/lib/editor/urls/detection.ts (2)

89-99: LGTM!

The isImageUrl function correctly handles edge cases by stripping query parameters and fragments before checking the extension. This ensures URLs like https://example.com/image.png?w=300 are properly recognized.


114-183: LGTM!

The syntax-agnostic detection strategy is well-implemented:

  • Comprehensive coverage of remote URLs, relative paths, and absolute paths
  • Proper overlap prevention to avoid duplicate matches
  • Appropriate stop characters to handle various syntaxes (Markdown, HTML, MDX)
  • Clear separation of concerns with three distinct regex patterns

This approach successfully works across different content formats without needing syntax-specific parsing.

docs/developer/image-preview-implementation.md (1)

1-217: Excellent documentation!

This is comprehensive, well-structured documentation that covers:

  • Architecture and component flow
  • Path resolution strategies for all three types
  • Performance optimization patterns with clear examples
  • Security considerations and validation
  • Configuration requirements

The rationale sections (like "Why Editor.tsx?") are particularly valuable for future maintainers.

src/hooks/editor/useImageHover.ts (4)

24-77: Excellent optimization to prevent unnecessary re-renders.

The logic is well-structured with good defensive checks. The conditional state update on lines 58-61 that avoids creating a new object when the URL hasn't changed is a smart optimization that will prevent downstream components from re-rendering unnecessarily.


79-96: Well-structured effect with proper cleanup.

The effect correctly manages the mousemove listener lifecycle, and clearing the hovered image on lines 88-91 when Alt is released is important defensive programming—it ensures state is cleared immediately without waiting for the next mousemove event.


99-113: Good optimization separating mouseleave into its own effect.

By depending only on view rather than also including isAltPressed and handleMouseMove, this effect avoids unnecessary re-runs and listener re-attachment. This is a thoughtful performance optimization.


1-116: Excellent implementation of the image hover detection hook.

The overall architecture is sound:

  • Clean separation between mousemove and mouseleave event handling
  • Thoughtful performance optimizations throughout
  • Good defensive programming with proper null checks and error handling
  • Well-documented with clear JSDoc comments
  • Proper TypeScript typing with exported interface

The hook integrates cleanly with CodeMirror's API and the detection utilities, and should work reliably for the image preview feature.

@dannysmith dannysmith merged commit a523262 into main Oct 22, 2025
8 checks passed
@dannysmith dannysmith deleted the image-previews branch October 22, 2025 16:27
This was referenced Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant