Skip to content

Feature/verse on image#963

Merged
FyreByrd merged 59 commits into
sillsdev:mainfrom
TheNonPirate:feature/verse-on-image
May 29, 2026
Merged

Feature/verse on image#963
FyreByrd merged 59 commits into
sillsdev:mainfrom
TheNonPirate:feature/verse-on-image

Conversation

@TheNonPirate

@TheNonPirate TheNonPirate commented May 22, 2026

Copy link
Copy Markdown
Contributor

Implementing #300 and building on #399. This implements the verse on image feature. Currently, everything is implemented except for the video download option.

Summary by CodeRabbit

Release Notes

  • New Features
    • Enabled text-on-image editing with adjustable text styling (font, size, bold, italic, alignment, letter spacing, line height, color, and text effects).
    • Added image manipulation controls including brightness, contrast, saturation, and blur adjustments.
    • Introduced image upload and interactive cropping workflow.
    • Added share and download functionality for edited images.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8df31d1f-aba1-45a9-b24b-924c2eda670f

📥 Commits

Reviewing files that changed from the base of the PR and between 5d4c37f and a26d13a.

📒 Files selected for processing (2)
  • src/lib/components/VerseOnImage.svelte
  • src/routes/image/upload/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/components/VerseOnImage.svelte

📝 Walkthrough

Walkthrough

This PR introduces the complete "verse on image" feature: users can now select/crop images, overlay and edit verse text with typography controls and image filters, and export as PNG for sharing. New pages at /image and /image/upload provide the editor and crop interface, with exports handled by html-to-image. The feature is now enabled in the feature config.

Changes

Verse on Image Feature

Layer / File(s) Summary
Store definitions and modal types
src/lib/data/stores/view.ts
Introduces voiCustomImage writable store with original and cropped image URLs, and extends ModalType with new modal identifiers for VerseOnImage and Download.
Icon assets and TypeScript typing updates
src/lib/icons/DownloadIcon.svelte, src/lib/icons/VideoIcon.svelte, src/lib/icons/image/*, src/lib/icons/index.ts
Adds new DownloadIcon and VideoIcon components and updates all image-editor icon components to use explicit TypeScript typing on Svelte $props() destructuring. All icons are exported from the centralized index.
Download selector modal component
src/lib/components/DownloadSelector.svelte
Renders a positioned modal with download action buttons, accepts downloadImage and downloadVideo callbacks, and exposes a showModal() method for programmatic triggering.
Image crop and selection route
src/routes/image/upload/+page.svelte
Implements gesture-based image cropping with pointer drag/resize and two-finger touch scaling. Validates cropped region, converts to PNG via canvas, updates voiCustomImage store, and navigates back to editor.
Verse on Image editor component
src/lib/components/VerseOnImage.svelte
Main editor with canvas-based image rendering, draggable/resizable verse text overlay, and comprehensive editor UI: background selection (preset or upload), font selection, bold/italic toggles, typography sliders (size, letter spacing, line height, textbox width %), color picker, text shadow modes, image filters (brightness, contrast, saturation, blur). Exports shareCanvas() and downloadCanvas() functions that use html-to-image to generate PNG for sharing/downloading.
Image editor route and integration
src/routes/image/+page.svelte, src/lib/components/TextSelectionToolbar.svelte
Adds /image page hosting the editor and modal; wires share/download handlers to component methods and manages modal dispatch via store. Updates TextSelectionToolbar to clear image state and navigate to editor when "text on image" button is clicked.
Feature activation, layout adjustment, and dependencies
convert/convertConfig.ts, src/routes/+layout.svelte, package.json
Uncomments text-on-image feature to enable it, adjusts layout boundary so TextAppearanceSelector renders outside SAB-only block, and adds html-to-image (runtime) and svelte-awesome-color-picker (dev) dependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sillsdev/appbuilder-pwa#960: Main PR's additions to src/lib/data/stores/view.ts extend the ModalType enum (adding VerseOnImage/Download) building directly on the ModalType refactor introduced in this PR.

Suggested reviewers

  • chrisvire
  • eomerdws

Poem

🐰 A rabbit hops through images bright,
Dragging verses left and right,
With colors bold and shadows cast,
We export the beauty—PNG at last!
✨📸✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/verse on image' is vague and uses a generic naming convention (Feature/) that does not clearly summarize the main change from a developer's perspective. Use a more descriptive title that clearly indicates the feature being added, e.g., 'Add verse on image editor with canvas export' or 'Implement text-on-image editing and sharing feature'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@TheNonPirate

Copy link
Copy Markdown
Contributor Author

Some of it still needs to be updated to Svelte 5 as well.

@FyreByrd FyreByrd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Well. The code itself works, and it works pretty well. I was able to generate an image following largely the same process that is present in the native app.

However, there are definitely quite a few design issues, most of which were inherited from the original PR, so I wouldn't feel too bad about it.

Here's what I noticed in addition to the individual review comments:

  1. The image page could use a little bit of padding/margin on the top and bottom.
  2. I used a really large image on the image upload page /crop. It may be good to have a maximum size for the image, at least when rendering to the page? It may also be beneficial to display the image dimensions somewhere on the page (or just the selection's dimensions)
  3. I think it would be helpful to disable native text selection within the image in the verse on image page. (I found it really annoying when trying to drag the text around that it kept trying to highlight).

Any additional thoughts @chrisvire ? I know you had said you had some issues with the original PR.

Comment thread static/firebase-config.js Outdated
Comment thread src/routes/image/+page.js Outdated
Comment thread src/routes/crop/+page.js Outdated
Comment thread src/lib/components/DownloadSelector.svelte

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please convert this to use TypeScript

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I've gotten it pretty well converted. Is there anything else you want me to change on the TypeScript front?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Several variables are shown as being possibly null/undefined and are showing errors in the editor.

Comment thread package.json Outdated
Comment thread src/lib/components/VerseOnImage.svelte
@TheNonPirate TheNonPirate force-pushed the feature/verse-on-image branch from 2eb3131 to eecf3aa Compare May 27, 2026 16:06
TheNonPirate and others added 26 commits May 27, 2026 10:14
…reference editor pane, start work on color picker editor.

Rebased and dealt with merge conflicts in
package-lock.json and package.json
…mple-color-picker with svelte-awesome-color-picker.

Fixed merge conflicts
(Because it isn't implemented yet)
@TheNonPirate TheNonPirate marked this pull request as ready for review May 28, 2026 18:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
src/lib/icons/VideoIcon.svelte (1)

7-7: ⚡ Quick win

Use typed $props() here as well for Svelte 5 consistency.

Line 7 still uses export let; aligning with the typed $props() pattern used across the migrated icons will keep the layer consistent and reduce migration drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/icons/VideoIcon.svelte` at line 7, Replace the old Svelte 3 export
pattern for the color prop with the typed Svelte 5 $props() pattern: remove
"export let color = 'black';" and instead use the $props() helper to declare a
typed props object (e.g., const { color = 'black' } = $props<{ color?: string
}>();), so the component uses the typed $props() call and preserves the default
value for the color prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/VerseOnImage.svelte`:
- Around line 312-327: The export clone appended to document.body inside
VerseOnImage.svelte (the variable clone used with toPng and later
fetch/shareImage) is only removed on success; modify the flow to always remove
the clone in all exit paths by moving document.body.removeChild(clone) into a
finally-style cleanup (or ensure both the toPng.catch and the fetch.catch call
remove the clone) so the DOM node is never left behind even if toPng or the
fetch fails; keep existing error logging but guarantee cleanup around the
toPng(...).then(...) chain that calls shareImage(reference, verses, reference +
'.png', blob).
- Around line 850-853: Replace the placeholder user-facing label "Test Label For
Now" passed to the ColorPicker component: locate the ColorPicker usage (props:
toRight, label, isInput) and change the label prop to the intended descriptive
string or an i18n key (e.g., use a translation function or a constant like
t('verseOnImage.colorLabel') or COLOR_PICKER_LABEL) so the UI shows the correct
production text instead of the placeholder.
- Around line 188-204: The component attaches global pointer listeners via
window.addEventListener('pointermove', resize) and
window.addEventListener('pointerup', stopResize) (see functions resize and
stopResize) but never cleans them up on unmount; add a Svelte onDestroy handler
(import onDestroy from 'svelte') that removes those same listeners (and sets
resizing = false if needed) to ensure no handlers remain after navigation or
component tear-down; ensure the onDestroy cleanup mirrors the removals in
stopResize so listeners attached around pointerdown/pointermove are always
removed.
- Line 497: The CSS rule in VerseOnImage.svelte uses max-height:
{textboxMaxHeight}; which is invalid when textboxMaxHeight is a numeric
value—ensure the value includes a unit by appending one (e.g.,
`${textboxMaxHeight}px`) or convert textboxMaxHeight to a string with units
before use; update the usage wherever max-height is set in the component (look
for textboxMaxHeight) so the rendered CSS always contains a valid unit (px, rem,
%, etc.) or bind a style object/inline style that formats the value with a unit.
- Around line 160-161: The pinch-resize math can produce Infinity/NaN when
resizeStartDistance is zero/uninitialized; in the pinch handler where scale is
computed (using currentDistance / resizeStartDistance) add a guard that checks
resizeStartDistance is a positive number (e.g., > 0) before dividing — if not,
set scale = 1 or skip the resize update — then compute newWidth from
resizeStartSize * scale (using the guarded scale) and bail out early if
resizeStartSize is also invalid; update the logic around the symbols scale,
currentDistance, resizeStartDistance, and resizeStartSize in VerseOnImage.svelte
accordingly.

In `@src/routes/crop/`+page.svelte:
- Around line 32-37: The onMount block and the confirm handler assume the DOM
image exists; guard against voiCustomImage.original being null by checking the
image variable before accessing properties or calling methods: in the onMount
handler (where you call image?.complete and image.addEventListener(...)) only
access image if it's truthy (e.g., early-return if !image), and in the confirm
action (the function that finalizes the crop) also short-circuit if image is
undefined or not complete; ensure you still attach the load listener only when
image is present and use { once: true } as before so initCropBox is called when
available. Reference: onMount, initCropBox, image, and the confirm action to
find and update the checks.
- Around line 39-43: initCropBox currently sets cropLeft/cropTop from
getImageRect(image) but doesn’t clamp cropSize to the rendered image, causing
negative positions when the image is smaller than the default 100; update
initCropBox to compute imageRect = getImageRect(image), then clamp cropSize =
Math.min(cropSize, imageRect.width, imageRect.height) and ensure cropLeft =
Math.max(imageRect.left, Math.min(cropLeft, imageRect.right - cropSize)) and
cropTop = Math.max(imageRect.top, Math.min(cropTop, imageRect.bottom -
cropSize)) (use the existing cropSize, cropLeft, cropTop variables) so the
initial overlay always fits inside the image.

In `@vite.config.js.timestamp-1689265782083-934aea1ea46f.mjs`:
- Around line 2-5: The committed timestamped vite config contains
machine-specific file:// imports (e.g., the imports that reference
NodeGlobalsPolyfillPlugin, NodeModulesPolyfillPlugin, rollupNodePolyFill and
sveltekit) and embedded sourcemap/metadata that leak local paths; remove this
generated file from the repo (git rm), add the filename pattern (e.g.,
vite.config.js.timestamp-*.mjs) to .gitignore, and restore/commit only the
canonical vite.config.js (or regenerate it via your build tool) so the hardcoded
file:/// imports and the sourcemap metadata entry are not tracked; ensure
CI/build steps produce these artifacts locally but they are not committed.

---

Nitpick comments:
In `@src/lib/icons/VideoIcon.svelte`:
- Line 7: Replace the old Svelte 3 export pattern for the color prop with the
typed Svelte 5 $props() pattern: remove "export let color = 'black';" and
instead use the $props() helper to declare a typed props object (e.g., const {
color = 'black' } = $props<{ color?: string }>();), so the component uses the
typed $props() call and preserves the default value for the color prop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea6239f8-efe7-4bd6-8354-026db09a5403

📥 Commits

Reviewing files that changed from the base of the PR and between 3793743 and 384cd47.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (37)
  • convert/convertConfig.ts
  • package.json
  • src/lib/components/DownloadSelector.svelte
  • src/lib/components/TextSelectionToolbar.svelte
  • src/lib/components/VerseOnImage.svelte
  • src/lib/data/stores/view.ts
  • src/lib/icons/DownloadIcon.svelte
  • src/lib/icons/TextAppearanceIcon.svelte
  • src/lib/icons/VideoIcon.svelte
  • src/lib/icons/image/BlurIcon.svelte
  • src/lib/icons/image/BrightnessIcon.svelte
  • src/lib/icons/image/ContrastIcon.svelte
  • src/lib/icons/image/FontChoiceIcon.svelte
  • src/lib/icons/image/FormatAlignCenterIcon.svelte
  • src/lib/icons/image/FormatAlignLeftIcon.svelte
  • src/lib/icons/image/FormatAlignRightIcon.svelte
  • src/lib/icons/image/FormatBoldIcon.svelte
  • src/lib/icons/image/FormatColorFillIcon.svelte
  • src/lib/icons/image/FormatItalicIcon.svelte
  • src/lib/icons/image/FormatLineSpacingIcon.svelte
  • src/lib/icons/image/ImageSingleIcon.svelte
  • src/lib/icons/image/ImagesIcon.svelte
  • src/lib/icons/image/LetterSpacingIcon.svelte
  • src/lib/icons/image/OpacityIcon.svelte
  • src/lib/icons/image/ReferenceIcon.svelte
  • src/lib/icons/image/ResizeArrowsIcon.svelte
  • src/lib/icons/image/SaturationIcon.svelte
  • src/lib/icons/image/TextGlowIcon.svelte
  • src/lib/icons/image/TextShadowIcon.svelte
  • src/lib/icons/image/TextShadowNoneIcon.svelte
  • src/lib/icons/image/TextWidthIcon.svelte
  • src/lib/icons/index.ts
  • src/routes/+layout.svelte
  • src/routes/crop/+page.svelte
  • src/routes/image/+page.svelte
  • vite.config.js.timestamp-1689265782083-934aea1ea46f.mjs
  • vite.config.js.timestamp-1692163299587-ad359ade3a45c.mjs
💤 Files with no reviewable changes (1)
  • src/routes/+layout.svelte

Comment thread src/lib/components/VerseOnImage.svelte
Comment thread src/lib/components/VerseOnImage.svelte
Comment thread src/lib/components/VerseOnImage.svelte
Comment thread src/lib/components/VerseOnImage.svelte Outdated
Comment thread src/lib/components/VerseOnImage.svelte Outdated
Comment thread src/routes/image/upload/+page.svelte
Comment thread src/routes/image/upload/+page.svelte
Comment thread vite.config.js.timestamp-1689265782083-934aea1ea46f.mjs Outdated

@FyreByrd FyreByrd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't be too much left to change. Just what I have here and also the CR comments.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Several variables are shown as being possibly null/undefined and are showing errors in the editor.

Comment thread src/lib/components/VerseOnImage.svelte
Comment thread src/lib/icons/image/BlurIcon.svelte
Comment thread src/routes/image/upload/+page.svelte

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/lib/components/VerseOnImage.svelte (3)

677-680: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove invalid // comment syntax in template.

HTML/Svelte markup doesn't support // comments. This line should be removed.

Proposed fix
                 <div
                     class="flex items-center justify-center image_selector_pane_box"
-                    //style="height: {this.width};"
                 >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/VerseOnImage.svelte` around lines 677 - 680, In the Svelte
template inside the VerseOnImage component remove the invalid JavaScript-style
comment fragment "//style="height: {this.width};"" from the <div class="flex
items-center justify-center image_selector_pane_box"> block (it isn't valid in
HTML/Svelte templates); if you intended to keep a commented note, replace it
with a proper HTML comment <!-- ... --> or remove it entirely and, if the inline
style is needed, convert it to a valid attribute style="height: {this.width};"
on that div.

93-97: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add null check for canvas context.

getContext('2d') can return null. Accessing fillStyle on null would throw a runtime error.

Proposed fix
     function standardize_color(str: string) {
         var ctx = document.createElement('canvas').getContext('2d');
+        if (!ctx) return str; // Fallback to original string if context unavailable
         ctx.fillStyle = str;
         return ctx.fillStyle;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/VerseOnImage.svelte` around lines 93 - 97, The function
standardize_color currently assumes
document.createElement('canvas').getContext('2d') always returns a
CanvasRenderingContext2D; add a null check for ctx in standardize_color and
handle the null case (e.g., return the original str or a safe fallback) before
accessing ctx.fillStyle; ensure you still set ctx.fillStyle = str when ctx is
non-null and return ctx.fillStyle, and keep variable names (ctx,
standardize_color) unchanged so the fix integrates cleanly.

359-383: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

downloadCanvas is missing the .finally() cleanup added to shareCanvas.

The clone appended to document.body is only removed on success (line 362). If toPng or the fetch chain fails, the hidden clone remains in the DOM.

Proposed fix
         document.body.appendChild(clone);
         toPng(clone)
             .then(function (dataUrl) {
                 document.body.removeChild(clone); //Get rid of the clone once we're done
                 fetch(dataUrl)
                     .then((response) => response.blob())
                     .then((blob) => {
                         const filename = reference + '.png';
                         const file = new File([blob], filename, { type: 'image/png' });
                         const url = URL.createObjectURL(file);
 
                         const anchor = document.createElement('a');
                         anchor.href = url;
                         anchor.download = filename;
                         anchor.click();
 
                         URL.revokeObjectURL(url);
                     })
                     .catch((error) => {
                         console.error('Error fetching data:', error);
                     });
             })
             .catch(function (error) {
                 console.error('oops, something went wrong!', error);
+            })
+            .finally(() => {
+                if (clone.isConnected) {
+                    document.body.removeChild(clone);
+                }
             });
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/VerseOnImage.svelte` around lines 359 - 383,
downloadCanvas currently only removes the DOM clone on successful
toPng()->fetch() resolution so a failed toPng or fetch leaves the hidden clone
in the document; update downloadCanvas to always remove the clone in a
.finally() after the toPng promise (or wrap the async flow in try/finally) and
also ensure any created object URL (URL.createObjectURL) is revoked in a finally
block so resources are cleaned up even on errors; target the clone variable, the
toPng(...) call, the fetch(...) chain, and the URL.revokeObjectURL(...) usage
when applying this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/components/VerseOnImage.svelte`:
- Around line 677-680: In the Svelte template inside the VerseOnImage component
remove the invalid JavaScript-style comment fragment "//style="height:
{this.width};"" from the <div class="flex items-center justify-center
image_selector_pane_box"> block (it isn't valid in HTML/Svelte templates); if
you intended to keep a commented note, replace it with a proper HTML comment
<!-- ... --> or remove it entirely and, if the inline style is needed, convert
it to a valid attribute style="height: {this.width};" on that div.
- Around line 93-97: The function standardize_color currently assumes
document.createElement('canvas').getContext('2d') always returns a
CanvasRenderingContext2D; add a null check for ctx in standardize_color and
handle the null case (e.g., return the original str or a safe fallback) before
accessing ctx.fillStyle; ensure you still set ctx.fillStyle = str when ctx is
non-null and return ctx.fillStyle, and keep variable names (ctx,
standardize_color) unchanged so the fix integrates cleanly.
- Around line 359-383: downloadCanvas currently only removes the DOM clone on
successful toPng()->fetch() resolution so a failed toPng or fetch leaves the
hidden clone in the document; update downloadCanvas to always remove the clone
in a .finally() after the toPng promise (or wrap the async flow in try/finally)
and also ensure any created object URL (URL.createObjectURL) is revoked in a
finally block so resources are cleaned up even on errors; target the clone
variable, the toPng(...) call, the fetch(...) chain, and the
URL.revokeObjectURL(...) usage when applying this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77d32bf9-85b7-4ee6-83bd-07a0c5c0172a

📥 Commits

Reviewing files that changed from the base of the PR and between 384cd47 and 5d4c37f.

📒 Files selected for processing (2)
  • src/lib/components/VerseOnImage.svelte
  • src/routes/crop/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/crop/+page.svelte

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Actionable comments posted: 0

@FyreByrd FyreByrd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good to me! All of my feedback has been addressed. Thanks for the good work.

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.

4 participants