Skip to content

Image fields in sidebar#39

Merged
dannysmith merged 22 commits intomainfrom
image-fields-in-sidebar
Oct 22, 2025
Merged

Image fields in sidebar#39
dannysmith merged 22 commits intomainfrom
image-fields-in-sidebar

Conversation

@dannysmith
Copy link
Copy Markdown
Owner

@dannysmith dannysmith commented Oct 22, 2025

Adds support for Astro's .image() helper, rendering a preview and file picker in the frontmatter sidebar where appropriate.

Currently not supported inside nested objects.

Partially addresses #21 - everything except support for image fields in nested objects.

Summary by CodeRabbit

  • New Features

    • Image field support in content collections with upload, copy-to-assets, and preview thumbnails.
    • New native file picker + drag-and-drop file selection UI for frontmatter image fields.
  • Improvements

    • Drag-and-drop handling enhanced with position-based validation and editor-bound checks.
    • Schema detection/watch now emits schema-change events for real-time updates.
    • Query client tuned with desktop-friendly caching defaults.
  • Documentation

    • Updated architecture and planning docs for component organization and image workflow phases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 22, 2025

Walkthrough

Adds frontmatter image support: new Tauri file utilities and commands, Image field type in schema parsing/merging, editor drag-drop position checks, FileUploadButton and ImageField/ImageThumbnail React components, watcher schema-change events, query/store updates, plus documentation and test content updates.

Changes

Cohort / File(s) Summary
Documentation
docs/developer/architecture-guide.md, docs/tasks-done/task-images-in-frontmatter.md, docs/tasks-todo/task-2-images-in-frontmatter.md, docs/tasks-todo/task-3-zod-parser-pattern-matching-rewrite.md, docs/tasks-todo/task-4-refactor-file-copying-logic.md
Added architecture guidance for UI/tauri component dirs and FileUploadButton example; added a phased plan for image-in-frontmatter support; added tasks for Zod parser rewrite and file-copy refactor; moved prior task content.
Tauri file commands
src-tauri/src/commands/files.rs, src-tauri/src/lib.rs
New commands is_path_in_project(file_path, project_path) -> bool and get_relative_path(file_path, project_path) -> Result<String, String>; registered in Tauri handler.
Watcher / schema-change
src-tauri/src/commands/watcher.rs, src/store/projectStore.ts
Watch additional schema-related paths; add is_schema_file detection; emit schema-changed events; frontend store subscribes and cleans up unlisten handles and invalidates collections on schema changes.
Schema parsing & merging (Rust)
src-tauri/src/parser.rs, src-tauri/src/schema_merger.rs
Introduced Image variant in ZodFieldType; parse/detect image() helper; propagate Image type through serialization; refactored Zod enhancement flow to return references + image field names and apply image-type overrides during schema merge.
Editor drag & drop (frontend)
src/lib/editor/dragdrop/types.ts, src/lib/editor/dragdrop/handlers.ts, src/lib/editor/dragdrop/index.ts, src/hooks/editor/useTauriListeners.ts, src/components/editor/Editor.tsx
Added position?: {x,y} to FileDropPayload; parseFileDropPayload now returns {paths, position}; added isDropWithinElement() and bounds-checking in handleTauriFileDrop; setup/cleanup of tauri drag-drop listeners and early-return guard for editor readiness.
Tauri UI helpers
src/components/tauri/FileUploadButton.tsx, src/components/tauri/index.ts
New FileUploadButton component (picker + drag-drop) with accept validation and isProcessing state; barrel export for Tauri components and props.
Frontmatter image UI
src/components/frontmatter/fields/ImageField.tsx, src/components/frontmatter/fields/ImageThumbnail.tsx, src/components/frontmatter/fields/FrontmatterField.tsx
New ImageField component with inline edit, FileUploadButton integration, project-path checks and copy-to-assets flows; ImageThumbnail component resolves local/remote images and shows preview; FrontmatterField now renders ImageField for image-type fields.
Schema typing & client config
src/lib/schema.ts, src/lib/query-client.ts
Added FieldType.Image = 'image' and mapping from 'image'; configured QueryClient with desktop-friendly defaults (no refetchOnWindowFocus, 5min staleTime, 10min gcTime).
Test fixtures / content
test/dummy-astro-project/.astro/collections/notes.schema.json, test/dummy-astro-project/.astro/content-assets.mjs, test/dummy-astro-project/src/content.config.ts, test/dummy-astro-project/src/content/articles/*, test/dummy-astro-project/src/content/notes/test-nested-image.md
Added coverImage field to schema and content frontmatter; populated content-assets map with image imports; converted notes schema to function form using image() helper; added test content demonstrating cover/coverImage usage.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant UI as ImageField UI
    participant Tauri
    participant FS as Filesystem
    participant Store as Frontmatter Store

    User->>UI: Click FileUploadButton / Drop file
    UI->>Tauri: open_dialog() or receive drop path
    Tauri-->>UI: absolute file path

    UI->>Tauri: is_path_in_project(path, projectRoot)
    Tauri->>FS: canonicalize & contains?
    Tauri-->>UI: true / false

    alt inside project
        UI->>Tauri: get_relative_path(path, projectRoot)
        Tauri-->>UI: relative path
        UI->>Store: update frontmatter with relative path
    else outside project
        UI->>Tauri: copy_file_to_assets(path, collection/assets)
        Tauri->>FS: copy to assets dir
        Tauri-->>UI: asset path
        UI->>Store: update frontmatter with asset path
    end

    UI->>UI: render ImageThumbnail -> resolve_image_path -> display preview
Loading
sequenceDiagram
    autonumber
    participant FSWatcher as Tauri Watcher
    participant Parser as Zod Parser
    participant Frontend as Frontend Store
    participant UI

    FSWatcher->>FSWatcher: detect file change
    FSWatcher->>FSWatcher: is_schema_file(path)?
    alt schema changed
        FSWatcher->>Parser: parse updated schema (detect Image)
        Parser-->>FSWatcher: schema + image fields
        FSWatcher->>Frontend: emit schema-changed
    else normal file
        FSWatcher->>Frontend: emit file-changed
    end

    Frontend->>Frontend: invalidate collections query
    Frontend->>UI: refresh schema -> render ImageField for image types
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • Image previews #38 — Related UI-level image preview/thumbnail work and Tauri path-resolution integration used by ImageThumbnail and resolve_image_path flows.

Poem

🐰
I hopped through code, from schema to tree,
A FileUploadButton brought files to me.
Thumbnails glimmer, paths tidy and neat,
Schemas learned "image" — frontmatter's treat.
Hop! Assets safe, previews snug and sweet.

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 fields in sidebar" directly and accurately describes the primary user-facing feature of this pull request. The changes across the codebase—including the new ImageField and ImageThumbnail components, FileUploadButton, backend parser enhancements, and Tauri command additions—all work together to enable rendering and managing image fields within the frontmatter sidebar. The title is concise, specific, and avoids vague phrasing or noise, making it clear to someone reviewing the history what the main objective is.
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-fields-in-sidebar

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 669c9f6 and 837ca9c.

📒 Files selected for processing (4)
  • src/components/frontmatter/fields/ImageField.tsx (1 hunks)
  • src/components/frontmatter/fields/ImageThumbnail.tsx (1 hunks)
  • src/components/tauri/FileUploadButton.tsx (1 hunks)
  • src/store/projectStore.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/store/projectStore.ts
🧰 Additional context used
📓 Path-based instructions (1)
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/frontmatter/fields/ImageField.tsx
  • src/components/tauri/FileUploadButton.tsx
  • src/components/frontmatter/fields/ImageThumbnail.tsx
🧬 Code graph analysis (3)
src/components/frontmatter/fields/ImageField.tsx (9)
src/types/common.ts (1)
  • FieldProps (7-11)
src/lib/schema.ts (1)
  • SchemaField (7-36)
src/store/editorStore.ts (2)
  • useEditorStore (219-490)
  • getNestedValue (77-95)
src/store/projectStore.ts (1)
  • useProjectStore (46-425)
src/lib/project-registry/path-resolution.ts (1)
  • getEffectiveAssetsDirectory (58-79)
src/lib/constants.ts (1)
  • ASTRO_PATHS (2-8)
src/components/frontmatter/fields/FieldWrapper.tsx (1)
  • FieldWrapper (22-62)
src/components/tauri/FileUploadButton.tsx (1)
  • FileUploadButton (70-222)
src/components/frontmatter/fields/ImageThumbnail.tsx (1)
  • ImageThumbnail (12-81)
src/components/tauri/FileUploadButton.tsx (2)
src/components/tauri/index.ts (2)
  • FileUploadButtonProps (9-9)
  • FileUploadButton (8-8)
src/components/ui/button.tsx (1)
  • buttonVariants (60-60)
src/components/frontmatter/fields/ImageThumbnail.tsx (2)
src/store/projectStore.ts (1)
  • useProjectStore (46-425)
src/store/editorStore.ts (1)
  • useEditorStore (219-490)
⏰ 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 (3)
src/components/frontmatter/fields/ImageField.tsx (2)

145-161: LGTM: Manual edit handling is sensible.

The approach of not validating manually entered paths is appropriate—invalid paths will simply fail to load in the preview, giving immediate visual feedback without blocking the user.


163-249: LGTM: Well-structured render logic.

The UI hierarchy is intuitive (input → button → preview), keyboard shortcuts enhance usability, and conditional rendering keeps the interface clean when no image is selected.

src/components/tauri/FileUploadButton.tsx (1)

169-179: LGTM: Extension validation is robust.

The validation correctly filters unwanted file types and provides helpful DEV logging without spamming production users with errors.


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: 11

🧹 Nitpick comments (18)
docs/tasks-todo/task-4-refactor-file-copying-logic.md (1)

1-220: Minor copyedits for clarity and lint

Capitalize “Markdown” and replace bare URLs with link text to satisfy MD034. Content/plan reads well.

src-tauri/src/commands/watcher.rs (1)

112-154: Debounce can drop the final batch; consider a trailing flush

process_events only runs after a >500ms gap followed by another event. If no further events arrive, buffered changes (incl. schema_changed) won’t emit. Recommend adding a timer-based trailing flush or a short sleep/drain loop.

Minimal tweak inside the loop:

// Replace the current debounce with:
use tokio::time::{sleep, Duration};

tokio::spawn(async move {
    let mut event_buffer = Vec::new();
    let mut last_event_time = std::time::Instant::now();
    loop {
        match rx.recv_timeout(Duration::from_millis(100)) {
            Ok(event) => {
                event_buffer.push(event);
                last_event_time = std::time::Instant::now();
            }
            Err(mpsc::RecvTimeoutError::Timeout) => {
                if !event_buffer.is_empty()
                    && last_event_time.elapsed() > Duration::from_millis(500)
                {
                    process_events(&app_handle, &mut event_buffer).await;
                    event_buffer.clear();
                }
            }
            Err(_) => break, // channel closed; flush before exit
        }
    }
    if !event_buffer.is_empty() {
        process_events(&app_handle, &mut event_buffer).await;
    }
});

This preserves debounce and guarantees a trailing flush.

src-tauri/src/schema_merger.rs (2)

711-742: Image type override works; ensure nested and array cases are covered by tests

Override from string→image and array→array is correct. Please add tests asserting:

  • nested path like coverImage.image becomes image
  • array of images becomes array sub_type=image

744-792: Unified Zod extraction is clear; minor: don’t else-if array ref case

Using else-if means a field with both referenced_collection and array_reference_collection (unlikely but safe) only records one. Consider independent checks for clarity.

-        if let Some(collection) = field.referenced_collection {
-            reference_map.insert(field.name.clone(), collection);
-        }
-        // Array reference
-        else if let Some(collection) = field.array_reference_collection {
-            reference_map.insert(field.name.clone(), collection);
-        }
+        if let Some(collection) = field.referenced_collection {
+            reference_map.insert(field.name.clone(), collection);
+        }
+        if let Some(collection) = field.array_reference_collection {
+            reference_map.insert(field.name.clone(), collection);
+        }
src/components/editor/Editor.tsx (1)

192-208: Tauri drag-drop listener: solid setup and guarded errors.
Good: await listen, capture UnlistenFn, and pass payload+view to handler. Consider logging the handler result in DEV for easier diagnostics.

-        unlistenDrop = await listen('tauri://drag-drop', event => {
-          void handleTauriFileDrop(event.payload, view)
-        })
+        unlistenDrop = await listen('tauri://drag-drop', async event => {
+          const res = await handleTauriFileDrop(event.payload, view)
+          if (import.meta.env.DEV && !res.success) {
+            // eslint-disable-next-line no-console
+            console.warn('[DROP] failed:', res.error)
+          }
+        })
src-tauri/src/commands/files.rs (1)

999-1016: Path containment check is correct and safe. Add tests.
Canonicalize + starts_with is appropriate. Please add unit tests for positive/negative cases (incl. symlink into/out of project if applicable).

docs/tasks-todo/task-3-zod-parser-pattern-matching-rewrite.md (1)

58-61: Avoid fragile line-number references; anchor by function name instead.

Line numbers drift quickly. Refer to function names and modules only (e.g., “parser.rs: parse_schema_fields()”) or add code anchors in comments.

docs/developer/architecture-guide.md (1)

142-176: Document the editor container data-attribute required by drop-bounds checks.

Drag/drop now checks closest('[data-editor-container]'). Please add a note that the editor root element must include data-editor-container to enable correct bounds gating.

src/components/frontmatter/fields/ImageThumbnail.tsx (2)

19-55: Add unmount-safety in async effect to avoid setState on unmounted component.

Prevent rare React warnings by guarding updates after unmount.

Apply this diff:

   useEffect(() => {
+    let cancelled = false
     if (!path || !projectPath) {
       setImageUrl(null)
       setLoadingState('idle')
       return
     }

     const loadImage = async () => {
       setLoadingState('loading')

       try {
@@
-        setImageUrl(assetUrl)
-        setLoadingState('success')
+        if (!cancelled) {
+          setImageUrl(assetUrl)
+          setLoadingState('success')
+        }
       } catch {
         // Fail silently - don't show error state
-        setLoadingState('error')
+        if (!cancelled) setLoadingState('error')
       }
     }

-    void loadImage()
+    void loadImage()
+    return () => {
+      cancelled = true
+    }
   }, [path, projectPath, currentFile?.path])

71-79: Improve accessibility: alt should reflect the image path or label.

Use a descriptive alt; fallback to file name.

Apply this diff:

-        <img
-          src={imageUrl}
-          alt="Preview"
+        <img
+          src={imageUrl}
+          alt={`Image preview: ${path.split('/').pop() || path}`}
src/components/frontmatter/fields/ImageField.tsx (1)

42-44: Reduce re-renders by selecting only the field’s value.

Optional: subscribe to just the nested frontmatter value to avoid full frontmatter-driven re-renders.

Sketch:

const fieldValue = useEditorStore(s => getNestedValue(s.frontmatter, name))
src/lib/editor/dragdrop/handlers.ts (3)

69-86: Gate debug logs behind DEV flag.

Avoid noisy logs in production.

Apply this diff:

-  // eslint-disable-next-line no-console
-  console.log('[DROP] Handler called, editorView:', !!editorView)
+  if (import.meta.env.DEV) {
+    // eslint-disable-next-line no-console
+    console.log('[DROP] Handler called, editorView:', !!editorView)
+  }
@@
-  // eslint-disable-next-line no-console
-  console.log('[DROP] Files:', filePaths.length, 'Position:', position)
+  if (import.meta.env.DEV) {
+    // eslint-disable-next-line no-console
+    console.log('[DROP] Files:', filePaths.length, 'Position:', position)
+  }

Repeat the same pattern for other console.log calls below.


93-99: Fallback if container attribute isn’t present.

Use editorView.dom when [data-editor-container] isn’t found.

Apply this diff:

-  const editorElement = editorView.dom.closest('[data-editor-container]')
+  const editorElement =
+    editorView.dom.closest('[data-editor-container]') ?? editorView.dom

17-38: Return the declared FileDropPayload type for consistency.

Reuse the exported type to reduce divergence across modules.

Apply this diff:

 export const parseFileDropPayload = (
   payload: unknown
-): { paths: string[]; position?: { x: number; y: number } } => {
+): FileDropPayload => {
   let filePaths: string[] = []
   let position: { x: number; y: number } | undefined = undefined
@@
-    return { paths: [] }
+    return { paths: [] }
@@
-  return { paths: filePaths, position }
+  return { paths: filePaths, position }
docs/tasks-done/task-images-in-frontmatter.md (1)

56-56: Format URL as proper markdown link (optional).

The bare URL could be formatted as a proper markdown link for better readability and to satisfy markdown linting rules.

Apply this diff:

-It is not possible to know about this from the generated JSON schemas because they show any image fields as a string (the path to the file). So we have to do this by reading `content.config.json` Similar way to how we do it for references. See here for the docs on images in content collections: https://docs.astro.build/en/guides/images/#images-in-content-collections
+It is not possible to know about this from the generated JSON schemas because they show any image fields as a string (the path to the file). So we have to do this by reading `content.config.json` Similar way to how we do it for references. See here for the [docs on images in content collections](https://docs.astro.build/en/guides/images/#images-in-content-collections).
src/components/tauri/FileUploadButton.tsx (3)

168-173: Inconsistent async patterns between handlers.

The click handler uses async/await (lines 90-123), while the drag-drop handler uses Promise.resolve().finally(). Both work correctly, but using a consistent pattern improves code maintainability.

Consider extracting the file processing logic into a shared async function:

const processFile = React.useCallback(async (filePath: string) => {
  setIsProcessing(true)
  try {
    await onFileSelectRef.current(filePath)
  } finally {
    setIsProcessing(false)
  }
}, [])

Then use it in both handlers:

// In click handler:
await processFile(selected)

// In drag-drop handler:
void processFile(filePath)

149-153: Multiple dropped files: only first file processed.

When multiple files are dropped, only the first file is processed (line 152) without informing the user that additional files were ignored. This could be confusing if a user accidentally drops multiple files.

Consider logging a warning in DEV mode when multiple files are dropped:

             if (paths.length === 0) return

+            if (import.meta.env.DEV && paths.length > 1) {
+              // eslint-disable-next-line no-console
+              console.warn(`Multiple files dropped. Only processing the first file.`)
+            }
+
             // Take the first file
             const filePath = paths[0]

195-207: Consider adding visual processing feedback (optional).

The component disables the button during processing but provides no visual indication (like a spinner) that an operation is in progress. While the current design keeps the component flexible (consumers can customize children), users might be confused when the button becomes briefly unresponsive.

If you want to add built-in loading feedback, consider:

import { Loader2 } from 'lucide-react'

// In render:
<Button ...>
  {isProcessing && <Loader2 className="mr-2 size-4 animate-spin" />}
  {children}
</Button>

Alternatively, keep the current design and document that consumers can check for the disabled state to show their own loading indicators.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a523262 and 669c9f6.

📒 Files selected for processing (29)
  • docs/developer/architecture-guide.md (2 hunks)
  • docs/tasks-done/task-images-in-frontmatter.md (1 hunks)
  • docs/tasks-todo/task-2-images-in-frontmatter.md (0 hunks)
  • docs/tasks-todo/task-3-zod-parser-pattern-matching-rewrite.md (1 hunks)
  • docs/tasks-todo/task-4-refactor-file-copying-logic.md (1 hunks)
  • src-tauri/src/commands/files.rs (1 hunks)
  • src-tauri/src/commands/watcher.rs (3 hunks)
  • src-tauri/src/lib.rs (1 hunks)
  • src-tauri/src/parser.rs (6 hunks)
  • src-tauri/src/schema_merger.rs (7 hunks)
  • src/components/editor/Editor.tsx (2 hunks)
  • src/components/frontmatter/fields/FrontmatterField.tsx (2 hunks)
  • src/components/frontmatter/fields/ImageField.tsx (1 hunks)
  • src/components/frontmatter/fields/ImageThumbnail.tsx (1 hunks)
  • src/components/tauri/FileUploadButton.tsx (1 hunks)
  • src/components/tauri/index.ts (1 hunks)
  • src/hooks/editor/useTauriListeners.ts (1 hunks)
  • src/lib/editor/dragdrop/handlers.ts (2 hunks)
  • src/lib/editor/dragdrop/index.ts (1 hunks)
  • src/lib/editor/dragdrop/types.ts (1 hunks)
  • src/lib/query-client.ts (1 hunks)
  • src/lib/schema.ts (2 hunks)
  • src/store/projectStore.ts (1 hunks)
  • test/dummy-astro-project/.astro/collections/notes.schema.json (1 hunks)
  • test/dummy-astro-project/.astro/content-assets.mjs (1 hunks)
  • test/dummy-astro-project/src/content.config.ts (1 hunks)
  • test/dummy-astro-project/src/content/articles/2023-12-05-schema-testing-article.md (1 hunks)
  • test/dummy-astro-project/src/content/articles/2025-01-22-image-preview-test.md (1 hunks)
  • test/dummy-astro-project/src/content/notes/test-nested-image.md (1 hunks)
💤 Files with no reviewable changes (1)
  • docs/tasks-todo/task-2-images-in-frontmatter.md
🧰 Additional context used
📓 Path-based instructions (5)
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/parser.rs
  • src-tauri/src/commands/files.rs
  • src-tauri/src/commands/watcher.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/schema_merger.rs
docs/developer/architecture-guide.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update docs/developer/architecture-guide.md whenever adding new architectural patterns

Files:

  • docs/developer/architecture-guide.md
src/lib/schema.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Centralize Zod schema parsing for frontmatter in src/lib/schema.ts

Files:

  • src/lib/schema.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/frontmatter/fields/ImageField.tsx
  • src/components/tauri/FileUploadButton.tsx
  • src/components/editor/Editor.tsx
  • src/components/frontmatter/fields/FrontmatterField.tsx
  • src/components/frontmatter/fields/ImageThumbnail.tsx
src/store/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

In Zustand stores, dispatch CustomEvent from store functions (no React hooks in stores) for cross-layer communication

Files:

  • src/store/projectStore.ts
🧠 Learnings (1)
📚 Learning: 2025-10-20T20:14:57.077Z
Learnt from: CR
PR: dannysmith/astro-editor#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-20T20:14:57.077Z
Learning: Applies to src/content/config.ts : Keep Astro content collections configuration in src/content/config.ts and ensure valid syntax for schema parsing

Applied to files:

  • test/dummy-astro-project/src/content.config.ts
🧬 Code graph analysis (8)
src-tauri/src/commands/files.rs (1)
src-tauri/src/models/file_entry.rs (1)
  • new (20-70)
src/lib/editor/dragdrop/handlers.ts (2)
src/lib/editor/dragdrop/index.ts (3)
  • parseFileDropPayload (23-23)
  • FileDropPayload (38-38)
  • isDropWithinElement (24-24)
src/lib/editor/dragdrop/types.ts (1)
  • FileDropPayload (5-11)
src/components/frontmatter/fields/ImageField.tsx (8)
src/types/common.ts (1)
  • FieldProps (7-11)
src/lib/schema.ts (1)
  • SchemaField (7-36)
src/store/editorStore.ts (2)
  • useEditorStore (219-490)
  • getNestedValue (77-95)
src/store/projectStore.ts (1)
  • useProjectStore (42-405)
src/lib/project-registry/path-resolution.ts (1)
  • getEffectiveAssetsDirectory (58-79)
src/lib/constants.ts (1)
  • ASTRO_PATHS (2-8)
src/components/tauri/FileUploadButton.tsx (1)
  • FileUploadButton (70-208)
src/components/frontmatter/fields/ImageThumbnail.tsx (1)
  • ImageThumbnail (13-82)
src/components/tauri/FileUploadButton.tsx (2)
src/components/tauri/index.ts (2)
  • FileUploadButtonProps (9-9)
  • FileUploadButton (8-8)
src/components/ui/button.tsx (1)
  • buttonVariants (60-60)
src/store/projectStore.ts (2)
src/lib/query-client.ts (1)
  • queryClient (4-18)
src/lib/query-keys.ts (1)
  • queryKeys (3-40)
src/components/editor/Editor.tsx (1)
src/lib/editor/dragdrop/handlers.ts (1)
  • handleTauriFileDrop (69-193)
src/components/frontmatter/fields/FrontmatterField.tsx (1)
src/components/frontmatter/fields/ImageField.tsx (1)
  • ImageField (36-247)
src-tauri/src/lib.rs (1)
src-tauri/src/commands/files.rs (2)
  • is_path_in_project (1008-1016)
  • get_relative_path (1027-1038)
🪛 LanguageTool
docs/tasks-todo/task-3-zod-parser-pattern-matching-rewrite.md

[grammar] ~64-~64: Use a hyphen to join words.
Context: ...gested Approach 1. Create new pattern matching functions: ```rust fn f...

(QB_NEW_EN_HYPHEN)

docs/tasks-todo/task-4-refactor-file-copying-logic.md

[uncategorized] ~78-~78: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...y) 3. Maintains separation of concerns (markdown formatting stays in editor, UI logic st...

(MARKDOWN_NNP)


[uncategorized] ~152-~152: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ... with call to shared function - Keep markdown formatting separate (formatAsMarkdown...

(MARKDOWN_NNP)

docs/tasks-done/task-images-in-frontmatter.md

[style] ~11-~11: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...ped, it should be copied and renamed in exactly the same way as we currently do for images dragg...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)


[style] ~11-~11: ‘exactly the same’ might be wordy. Consider a shorter alternative.
Context: ...re of the current collection, again, in exactly the same way that we do with dragging and droppi...

(EN_WORDINESS_PREMIUM_EXACTLY_THE_SAME)


[grammar] ~13-~13: Ensure spelling is correct
Context: ...We can probably use similar code to the folating image previews we recently implemented....

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~14-~14: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...d to display the component and maybe we need to have a button that when you press it, i...

(REP_NEED_TO_VB)


[style] ~14-~14: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... the file or copies it or something. We need to think about the best UI for making this...

(REP_NEED_TO_VB)


[style] ~15-~15: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...ry this before, it got very complicated very quickly. ### Design Decisions UI Pattern:...

(EN_WEAK_ADJECTIVE)

🪛 markdownlint-cli2 (0.18.1)
docs/tasks-todo/task-4-refactor-file-copying-logic.md

56-56: Bare URL used

(MD034, no-bare-urls)

docs/tasks-done/task-images-in-frontmatter.md

56-56: Bare URL used

(MD034, no-bare-urls)

⏰ 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 (27)
src/lib/query-client.ts (1)

3-18: LGTM! Well-configured for desktop app context.

The QueryClient configuration is appropriate for a desktop application:

  • Disabling refetchOnWindowFocus prevents unnecessary refetches since file changes are handled by watchers
  • 5-minute staleTime and 10-minute gcTime provide good balance for a desktop app where data is relatively stable

The comments clearly explain the reasoning behind each choice.

src/lib/schema.ts (1)

47-61: Image type plumbing looks good

Enum + string mapping are consistent and safe; Unknown fallback preserved. No further changes needed.

Also applies to: 123-140

test/dummy-astro-project/.astro/collections/notes.schema.json (1)

77-88: Schema addition matches intended structure

coverImage with { image, alt } and additionalProperties:false looks correct and aligns with the content config. If this artifact is committed intentionally for tests, all good; otherwise consider excluding .astro outputs from VCS outside test fixtures.

test/dummy-astro-project/src/content.config.ts (1)

37-59: notes schema: image() integration looks correct

Function-form schema with image().optional() under coverImage is valid and matches the generated JSON schema. No changes needed.
If nested image fields aren’t yet rendered in the UI (per PR note), consider adding a small follow-up issue to track that gap.

src-tauri/src/schema_merger.rs (2)

204-211: Good: Zod-driven enhancement after JSON parse

Applying Zod info post-JSON preserves structure and augments types. Error handling via warn is fine.


862-875: Include Image in zod_type_to_field_type — good

Mapping covers Image; fallback remains Unknown for future types.

test/dummy-astro-project/.astro/content-assets.mjs (1)

1-7: Auto-generated file - no review needed.

This appears to be an auto-generated Astro content assets manifest that maps image imports to their resolved paths. The changes reflect the new image references added to test content files in this PR.

src/lib/editor/dragdrop/index.ts (1)

21-25: Clean API extension.

Exporting isDropWithinElement extends the drag-drop public API to support position-aware drop boundary validation, aligning well with the enhanced position tracking introduced in this PR.

test/dummy-astro-project/src/content/articles/2023-12-05-schema-testing-article.md (1)

12-12: Appropriate test content.

The addition of a relative image path in the cover field provides good test coverage for the new image field functionality.

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

9-9: Good test coverage.

The absolute path syntax in the cover field complements the relative path test in the other article, providing comprehensive test coverage for different image path formats.

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

11-14: Good defensive programming.

The early return prevents listener setup until the editor view is ready, avoiding potential null reference issues and unnecessary listener registration attempts.

src/lib/editor/dragdrop/types.ts (1)

7-10: Backward-compatible type extension.

The optional position field cleanly extends FileDropPayload to support position-aware drop handling while maintaining backward compatibility with existing code.

test/dummy-astro-project/src/content/notes/test-nested-image.md (1)

9-11: Clarify nested image field support.

The PR objectives state: "The change does not currently support .image() fields nested inside objects." However, this test file includes a nested coverImage.image field. Please clarify whether:

  1. This is intentional test data for future nested image support
  2. The nested image schema is supported but only the sidebar rendering is not yet implemented
  3. This test case should be removed or modified
src/components/frontmatter/fields/FrontmatterField.tsx (2)

14-14: Clean import addition.


152-157: Well-integrated image field handling.

The image field handler follows the established pattern used for other field types and is appropriately positioned in the conditional chain. The implementation adheres to the Direct Store Pattern guideline (confirmed by reviewing the ImageField component which uses useEditorStore() and useProjectStore.getState() directly).

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

4-4: Imports look good and align with the drag/drop refactor.
No issues found.

Also applies to: 9-9, 11-11

src/components/tauri/index.ts (1)

1-9: Barrel exports look good.
Clear and minimal API, no issues.

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

371-372: Commands exposed correctly.
Matches backend implementations; no issues.

src/components/frontmatter/fields/ImageField.tsx (3)

42-47: Direct Store Pattern ✅

Uses Zustand stores directly and getState() in async handlers. Aligns with our component guidelines.


82-106: All referenced Tauri commands exist and are properly registered.

Verification confirms that copy_file_to_assets, copy_file_to_assets_with_override, is_path_in_project, and get_relative_path are all implemented in src-tauri/src/commands/files.rs with the #[tauri::command] macro and registered in the generate_handler! macro in src-tauri/src/lib.rs. The code in ImageField.tsx correctly invokes these commands.

Likely an incorrect or invalid review comment.


14-16: Import path is correct as-is.

The index.ts file properly re-exports getEffectiveAssetsDirectory via export * from './path-resolution' (final line), confirming that importing from '../../../lib/project-registry' as a barrel export is valid and appropriate. No changes needed.

docs/tasks-done/task-images-in-frontmatter.md (1)

423-468: Phase 5 completion status unclear.

Phase 5 (Testing & Documentation) does not have a completion marker (✅) like Phases 0-4, but the PR appears to be ready for review. Clarify whether Phase 5 tasks have been completed or are pending.

If Phase 5 is complete, add the completion marker to maintain consistency with other phases.

src/components/tauri/FileUploadButton.tsx (5)

8-31: Well-structured types and props interface.

The type definitions are clear, well-documented, and appropriate for the Tauri drag-drop API. The props interface provides good flexibility with optional styling props.


36-50: Helper functions are correct and handle edge cases.

The extension validation logic properly handles files without extensions by returning false, which is the correct behavior.


79-87: Good use of refs to optimize effect dependencies.

Storing the callback in a ref is the correct pattern to avoid unnecessary effect re-runs while ensuring the latest callback is always used.


90-123: Click handler is well-implemented.

The file picker flow properly guards against concurrent operations, handles cancellation gracefully, and includes error handling. The file dialog filters should prevent selection of invalid file types, though explicit validation after selection could add an extra safety layer.


70-208: Component follows Direct Store Pattern guideline appropriately.

This reusable component correctly uses the callback pattern (onFileSelect) rather than directly accessing Zustand stores. This design allows the component to be used in various contexts. The coding guideline about Direct Store Pattern is more relevant for form fields that update specific application state, so this implementation is appropriate.

Based on coding guidelines.

@dannysmith dannysmith mentioned this pull request Oct 22, 2025
33 tasks
@dannysmith dannysmith merged commit 68a1e79 into main Oct 22, 2025
8 checks passed
@dannysmith dannysmith deleted the image-fields-in-sidebar branch October 22, 2025 23:35
@coderabbitai coderabbitai bot mentioned this pull request Nov 1, 2025
6 tasks
@coderabbitai coderabbitai bot mentioned this pull request Dec 4, 2025
@coderabbitai coderabbitai bot mentioned this pull request Dec 15, 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