Skip to content

Conversation

@enkei64
Copy link
Contributor

@enkei64 enkei64 commented Aug 10, 2025

Description

This PR includes the stickers panel from the Iconify API. You can browse, search, and add the stickers to your OpenCut project.

Fixes # (issue)
No existing issues were fixed.

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Test manually in the browser. Stickers are visible in the project, in the browser, and render properly.

Test Configuration:

  • Node version: v22.16.0
  • Browser (if applicable): Arc Browser
  • Operating System: MacOS 26 Tahoe

Screenshots (if applicable)

CleanShot 2025-08-10 at 14 58 19 CleanShot 2025-08-10 at 14 58 44 CleanShot 2025-08-10 at 14 59 02 CleanShot 2025-08-10 at 14 59 24

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have added screenshots if ui has been changed
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Additional context

Add any other context about the pull request here.

Summary by CodeRabbit

  • New Features

    • Introduced a new Stickers tab in the media panel, allowing users to browse, search, and add stickers/icons from various collections.
    • Users can filter stickers by category, view popular collections, and search for specific icons.
    • Recent stickers are tracked for quick access, and attribution to Iconify is displayed.
  • Bug Fixes

    • Improved handling of image files without MIME types to ensure SVG images display correctly when loaded.
  • Chores

    • Enhanced project data storage to include canvas size and mode for better project restoration.

@vercel
Copy link

vercel bot commented Aug 10, 2025

@enkeii64 is attempting to deploy a commit to the OpenCut OSS Team on Vercel.

A member of the Team first needs to authorize it.

@netlify
Copy link

netlify bot commented Aug 10, 2025

👷 Deploy request for appcut pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 0df29a4

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 10, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

A new stickers feature was implemented in the media panel, enabling users to browse, search, and add stickers/icons from Iconify collections. This involved integrating a new StickersView component, creating a Zustand stickers store for state management, and introducing an Iconify API client for icon data retrieval and SVG handling. Minor enhancements were also made to media file loading and project serialization.

Changes

Cohort / File(s) Change Summary
Media Panel Stickers Integration
apps/web/src/components/editor/media-panel/index.tsx
Replaces the stickers tab placeholder with the actual <StickersView /> component and adds its import.
Stickers UI & Logic
apps/web/src/components/editor/media-panel/views/stickers.tsx
Adds the StickersView React component and internal StickerItem for browsing, searching, and adding stickers from Iconify. Implements UI for category selection, search, collection browsing, and sticker addition with error handling and attribution.
Iconify API Client
apps/web/src/lib/iconify-api.ts
Introduces an API client for Iconify-compatible hosts with host fallback, icon/collection fetching, search, SVG URL building, SVG download, and conversion utilities. Exports interfaces for icon data, grouped collections, and category extraction.
Stickers Store
apps/web/src/stores/stickers-store.ts
Adds a Zustand store for stickers, managing search, category, collection, view mode, collections data, search results, recent stickers, and async actions for loading, searching, downloading, and recent sticker management.
Media Storage Enhancements
apps/web/src/lib/storage/storage-service.ts
Updates project serialization to include canvasSize and canvasMode. Improves image file loading by handling SVGs without MIME types, ensuring correct object URL creation for SVG images.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MediaPanel
    participant StickersView
    participant StickersStore
    participant IconifyAPI
    participant MediaStore

    User->>MediaPanel: Selects "Stickers" tab
    MediaPanel->>StickersView: Renders StickersView
    StickersView->>StickersStore: Loads collections/searches stickers
    StickersStore->>IconifyAPI: Fetch collections/search icons
    IconifyAPI-->>StickersStore: Return collections/search results
    StickersStore-->>StickersView: Update state with results
    User->>StickersView: Clicks "Add" on sticker
    StickersView->>StickersStore: downloadSticker(iconName)
    StickersStore->>IconifyAPI: Download SVG
    IconifyAPI-->>StickersStore: Return SVG as File
    StickersStore->>MediaStore: Add sticker file to media library
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

🐰✨
In the meadow of code where the stickers now bloom,
Iconify brings magic to chase away gloom.
With search and with browse, the icons abound,
In grids and collections, new treasures are found.
A hop, a click, a sticker to see—
Such joy in the media panel, for you and for me!
🖼️🌟

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@enkei64 enkei64 changed the base branch from main to staging August 10, 2025 05:02
@enkei64 enkei64 marked this pull request as ready for review August 10, 2025 06:11
@enkei64
Copy link
Contributor Author

enkei64 commented Aug 10, 2025

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@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: 4

🧹 Nitpick comments (7)
apps/web/src/lib/iconify-api.ts (2)

11-18: Prefer currentHost first in the fallback order to avoid unnecessary retries

Always trying ICONIFY_HOSTS in the same order can add avoidable latency if a previous call already succeeded on a secondary host. Start with currentHost, then try the others.

-  for (const host of ICONIFY_HOSTS) {
+  const hosts = [currentHost, ...ICONIFY_HOSTS.filter((h) => h !== currentHost)];
+  for (const host of hosts) {
     try {
       const response = await fetch(`${host}${path}`, {
         signal: AbortSignal.timeout(2000),
       });
       if (response.ok) {
         currentHost = host;
         return response;
       }
     } catch (error) {
       console.warn(`Failed to fetch from ${host}:`, error);
     }
   }

14-15: AbortSignal.timeout may not be universally supported in older browsers

This can throw in older Safari. Consider a small fallback using AbortController + setTimeout if AbortSignal.timeout is unavailable.

apps/web/src/lib/storage/storage-service.ts (1)

164-179: Avoid reading the entire file just to sniff SVG; handle XML prolog too

Reading the whole blob for detection is wasteful. Sniff a small slice and tolerate XML prolog/comments before .

-    if (metadata.type === "image" && (!file.type || file.type === "")) {
+    if (metadata.type === "image" && (!file.type || file.type === "")) {
       try {
-        const text = await file.text();
-        if (text.trim().startsWith("<svg")) {
+        const head = await file.slice(0, 512).text();
+        const trimmed = head.trimStart();
+        if (/<svg[\s>]/i.test(trimmed)) {
           const svgBlob = new Blob([text], { type: "image/svg+xml" });
           url = URL.createObjectURL(svgBlob);
         } else {
           url = URL.createObjectURL(file);
         }
       } catch {
         url = URL.createObjectURL(file);
       }
     } else {
       url = URL.createObjectURL(file);
     }
apps/web/src/components/editor/media-panel/views/stickers.tsx (4)

332-334: Avoid as any by narrowing the tab value to the union type

Keeps type safety and prevents invalid categories.

-        <Tabs
-          value={selectedCategory}
-          onValueChange={(v) => setSelectedCategory(v as any)}
-        >
+        <Tabs
+          value={selectedCategory}
+          onValueChange={(v) =>
+            setSelectedCategory(v as "all" | "general" | "brands" | "emoji")
+          }
+        >

292-301: Add accessible name to the clear-search icon button

Icon-only buttons need an aria-label.

-            <button
+            <button
+              aria-label="Clear search"
               className="absolute right-1 top-1/2 -translate-y-1/2 h-6 w-6 p-0 rounded hover:bg-accent flex items-center justify-center"
               onClick={() => {
                 setLocalSearchQuery("");
                 setSearchQuery("");
                 setViewMode("browse");
               }}
             >

365-371: Add accessible name to the clear-recents icon button

Improves screen-reader usability.

-                    <button
+                    <button
+                      aria-label="Clear recent stickers"
                       onClick={clearRecentStickers}
                       className="ml-auto h-5 w-5 p-0 rounded hover:bg-accent flex items-center justify-center"
                     >

73-146: Reduce TooltipProvider duplication

TooltipProvider is relatively heavy; wrapping the list once (outside StickerItem) avoids creating a provider per item.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa698c2 and 7a69c9a.

📒 Files selected for processing (5)
  • apps/web/src/components/editor/media-panel/index.tsx (2 hunks)
  • apps/web/src/components/editor/media-panel/views/stickers.tsx (1 hunks)
  • apps/web/src/lib/iconify-api.ts (1 hunks)
  • apps/web/src/lib/storage/storage-service.ts (3 hunks)
  • apps/web/src/stores/stickers-store.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Include caption tracks for audio and video elements.

Applied to files:

  • apps/web/src/components/editor/media-panel/index.tsx
  • apps/web/src/components/editor/media-panel/views/stickers.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Don't include "image", "picture", or "photo" in img alt prop.

Applied to files:

  • apps/web/src/components/editor/media-panel/index.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Use semantic elements instead of role attributes in JSX.

Applied to files:

  • apps/web/src/components/editor/media-panel/index.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Use `<>...</>` instead of `<Fragment>...</Fragment>`.

Applied to files:

  • apps/web/src/components/editor/media-panel/index.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
PR: OpenCut-app/OpenCut#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `<img>` elements in Next.js projects.

Applied to files:

  • apps/web/src/components/editor/media-panel/index.tsx
🔇 Additional comments (2)
apps/web/src/components/editor/media-panel/index.tsx (1)

8-8: Integrates StickersView correctly

Import + view map wiring looks good. Non-breaking, scoped to the stickers tab.

Also applies to: 20-20

apps/web/src/stores/stickers-store.ts (1)

49-189: Solid store structure and error handling

State shape, actions, and async flows (including recent stickers) look clean and predictable. Good job keeping UI concerns out of the store.

Comment on lines 88 to 111
<img
src={
hostIndex === 0
? getIconSvgUrl(iconName, { width: 64, height: 64 })
: buildIconSvgUrl(
ICONIFY_HOSTS[
Math.min(hostIndex, ICONIFY_HOSTS.length - 1)
],
iconName,
{ width: 64, height: 64 }
)
}
alt={displayName}
className="w-full h-full object-contain"
onError={() => {
const next = hostIndex + 1;
if (next < ICONIFY_HOSTS.length) {
setHostIndex(next);
} else {
setImageError(true);
}
}}
loading="lazy"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace with Next.js per project guidelines and to enable optimizations

Organizational standard: “Don’t use elements in Next.js projects.” Switch to next/image and add the import. For SVGs from remote hosts, set unoptimized and ensure hosts are permitted in next.config.

+import Image from "next/image";
@@
-              <img
+              <Image
                 src={
                   hostIndex === 0
                     ? getIconSvgUrl(iconName, { width: 64, height: 64 })
                     : buildIconSvgUrl(
                         ICONIFY_HOSTS[
                           Math.min(hostIndex, ICONIFY_HOSTS.length - 1)
                         ],
                         iconName,
                         { width: 64, height: 64 }
                       )
                 }
                 alt={displayName}
+                width={64}
+                height={64}
                 className="w-full h-full object-contain"
                 onError={() => {
                   const next = hostIndex + 1;
                   if (next < ICONIFY_HOSTS.length) {
                     setHostIndex(next);
                   } else {
                     setImageError(true);
                   }
                 }}
-                loading="lazy"
-              />
+                loading="lazy"
+                unoptimized
+              />

Also add allowed remote patterns (api.iconify.design, api.simplesvg.com, api.unisvg.com) in next.config images.remotePatterns.

I can provide the next.config addition if helpful.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img
src={
hostIndex === 0
? getIconSvgUrl(iconName, { width: 64, height: 64 })
: buildIconSvgUrl(
ICONIFY_HOSTS[
Math.min(hostIndex, ICONIFY_HOSTS.length - 1)
],
iconName,
{ width: 64, height: 64 }
)
}
alt={displayName}
className="w-full h-full object-contain"
onError={() => {
const next = hostIndex + 1;
if (next < ICONIFY_HOSTS.length) {
setHostIndex(next);
} else {
setImageError(true);
}
}}
loading="lazy"
/>
// At the top of apps/web/src/components/editor/media-panel/views/stickers.tsx
import Image from "next/image";
// … other imports and code …
<Image
src={
hostIndex === 0
? getIconSvgUrl(iconName, { width: 64, height: 64 })
: buildIconSvgUrl(
ICONIFY_HOSTS[
Math.min(hostIndex, ICONIFY_HOSTS.length - 1)
],
iconName,
{ width: 64, height: 64 }
)
}
alt={displayName}
width={64}
height={64}
className="w-full h-full object-contain"
onError={() => {
const next = hostIndex + 1;
if (next < ICONIFY_HOSTS.length) {
setHostIndex(next);
} else {
setImageError(true);
}
}}
loading="lazy"
unoptimized
/>
🤖 Prompt for AI Agents
In apps/web/src/components/editor/media-panel/views/stickers.tsx around lines 88
to 111, replace the <img> element with Next.js <Image> component by importing it
from 'next/image'. Set the unoptimized prop to true for remote SVGs and keep the
existing props like src, alt, className, onError, and loading. Also, update
next.config.js to include the allowed remotePatterns for api.iconify.design,
api.simplesvg.com, and api.unisvg.com under images.remotePatterns to permit
these hosts for image optimization.

Comment on lines +156 to +158
if (params?.color) {
urlParams.append("color", params.color.replace("#", "%23"));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Double-encoding bug for color parameter

Manually replacing “#” with “%23” and then passing through URLSearchParams produces “%2523...” in the final URL. Let URLSearchParams handle encoding.

-  if (params?.color) {
-    urlParams.append("color", params.color.replace("#", "%23"));
-  }
+  if (params?.color) {
+    urlParams.append("color", params.color);
+  }
🤖 Prompt for AI Agents
In apps/web/src/lib/iconify-api.ts around lines 156 to 158, the code manually
replaces "#" with "%23" in the color parameter before appending it to
URLSearchParams, causing double encoding ("%2523"). Remove the manual
replacement and append the raw color string directly to urlParams so that
URLSearchParams can handle encoding correctly.

Comment on lines +187 to +197
export async function downloadSvgAsText(
iconName: string,
params?: Parameters<typeof getIconSvgUrl>[1]
): Promise<string> {
const url = getIconSvgUrl(iconName, params);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to download SVG: ${response.statusText}`);
}
return await response.text();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Download should use the same host-fallback with timeout

This currently fetches a single URL without fallback or timeout. Use fetchWithFallback on the icon path to match the resilience used elsewhere.

-export async function downloadSvgAsText(
-  iconName: string,
-  params?: Parameters<typeof getIconSvgUrl>[1]
-): Promise<string> {
-  const url = getIconSvgUrl(iconName, params);
-  const response = await fetch(url);
-  if (!response.ok) {
-    throw new Error(`Failed to download SVG: ${response.statusText}`);
-  }
-  return await response.text();
-}
+export async function downloadSvgAsText(
+  iconName: string,
+  params?: Parameters<typeof getIconSvgUrl>[1]
+): Promise<string> {
+  const [prefix, name] = iconName.includes(":")
+    ? iconName.split(":")
+    : ["", iconName];
+  if (!prefix || !name) {
+    throw new Error('Invalid icon name format. Expected "prefix:name"');
+  }
+  const urlParams = new URLSearchParams();
+  if (params?.color) urlParams.append("color", params.color);
+  if (params?.width) urlParams.append("width", params.width.toString());
+  if (params?.height) urlParams.append("height", params.height.toString());
+  if (params?.flip) urlParams.append("flip", params.flip);
+  if (params?.rotate) urlParams.append("rotate", params.rotate.toString());
+  const queryString = urlParams.toString();
+  const path = `/${prefix}/${name}.svg${queryString ? "?" + queryString : ""}`;
+
+  const response = await fetchWithFallback(path);
+  if (!response.ok) {
+    throw new Error(`Failed to download SVG: ${response.statusText}`);
+  }
+  return await response.text();
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function downloadSvgAsText(
iconName: string,
params?: Parameters<typeof getIconSvgUrl>[1]
): Promise<string> {
const url = getIconSvgUrl(iconName, params);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to download SVG: ${response.statusText}`);
}
return await response.text();
}
export async function downloadSvgAsText(
iconName: string,
params?: Parameters<typeof getIconSvgUrl>[1]
): Promise<string> {
const [prefix, name] = iconName.includes(":")
? iconName.split(":")
: ["", iconName];
if (!prefix || !name) {
throw new Error('Invalid icon name format. Expected "prefix:name"');
}
const urlParams = new URLSearchParams();
if (params?.color) urlParams.append("color", params.color);
if (params?.width) urlParams.append("width", params.width.toString());
if (params?.height) urlParams.append("height", params.height.toString());
if (params?.flip) urlParams.append("flip", params.flip);
if (params?.rotate) urlParams.append("rotate", params.rotate.toString());
const queryString = urlParams.toString();
const path = `/${prefix}/${name}.svg${queryString ? "?" + queryString : ""}`;
const response = await fetchWithFallback(path);
if (!response.ok) {
throw new Error(`Failed to download SVG: ${response.statusText}`);
}
return await response.text();
}
🤖 Prompt for AI Agents
In apps/web/src/lib/iconify-api.ts around lines 187 to 197, the
downloadSvgAsText function uses a simple fetch call without fallback or timeout,
which reduces resilience. Replace the fetch call with fetchWithFallback using
the icon URL to implement the same host-fallback and timeout logic used
elsewhere. This will ensure the download is more robust by trying alternative
hosts and respecting timeouts.

Comment on lines 77 to 79
canvasSize: project.canvasSize,
canvasMode: project.canvasMode,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Back-compat defaults for new project fields (canvasSize/canvasMode)

Loading older projects saved before these fields existed may yield undefined. Ensure TProject tolerates that or provide sane defaults on load.

If TProject requires non-optional values, consider defaulting here:

-      canvasSize: serializedProject.canvasSize,
-      canvasMode: serializedProject.canvasMode,
+      canvasSize: serializedProject.canvasSize ?? /* get project default here */,
+      canvasMode: serializedProject.canvasMode ?? /* get project default here */,

To verify impact across the repo, run:

Also applies to: 101-103


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "TProject definition (first match with context):"
rg -n -A 20 -B 5 $'\\b(type|interface)\\s+TProject\\b'
echo
echo "Usages of canvasSize/canvasMode:"
rg -n 'canvasSize|canvasMode' -A 2 -B 2

Length of output: 10674


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Context around serializedProject usage:"
rg -n 'serializedProject' -C5 apps/web/src/lib/storage/storage-service.ts

Length of output: 1779


Apply back-compat defaults for canvasSize/canvasMode in loadProject

In apps/web/src/lib/storage/storage-service.ts, the loadProject method returns a TProject whose canvasSize and canvasMode are required by the TProject interface but may be undefined when loading older serialized data. Add safe defaults here:

• File: apps/web/src/lib/storage/storage-service.ts
Location: return block of async loadProject(...) (around lines 101–103)

Suggested change:

     return {
       id: serializedProject.id,
       name: serializedProject.name,
       thumbnail: serializedProject.thumbnail,
       createdAt: new Date(serializedProject.createdAt),
       updatedAt: new Date(serializedProject.updatedAt),
       backgroundColor: serializedProject.backgroundColor,
       backgroundType: serializedProject.backgroundType,
       blurIntensity: serializedProject.blurIntensity,
       bookmarks: serializedProject.bookmarks,
       fps: serializedProject.fps,
-      canvasSize: serializedProject.canvasSize,
-      canvasMode: serializedProject.canvasMode,
+      canvasSize: serializedProject.canvasSize ?? DEFAULT_CANVAS_SIZE,
+      canvasMode: serializedProject.canvasMode ?? "preset",
     };

Don’t forget to import DEFAULT_CANVAS_SIZE (and any mode‐default constant) at the top of the file so these fallbacks resolve correctly.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/src/lib/storage/storage-service.ts around lines 101 to 103 in the
return block of the async loadProject method, canvasSize and canvasMode may be
undefined for older serialized data but are required by the TProject interface.
Fix this by applying safe default values for canvasSize and canvasMode using
DEFAULT_CANVAS_SIZE and a suitable default mode constant. Also, ensure you
import DEFAULT_CANVAS_SIZE and the mode default constant at the top of the file
so these fallbacks resolve correctly.

@fathisiddiqi
Copy link
Contributor

I think this pr is not suitable for the sticker purpose. I've checked the CapCut editor, and they use sticker instead of svg icon

you can refer to stickerl

@mazeincoding mazeincoding merged commit c3f3345 into OpenCut-app:staging Aug 15, 2025
2 of 3 checks passed
@enkei64
Copy link
Contributor Author

enkei64 commented Aug 15, 2025

I think this pr is not suitable for the sticker purpose. I've checked the CapCut editor, and they use sticker instead of svg icon

you can refer to stickerl

This is understandable. I do believe the "stickers" from iconify.design are more like icons, but I couldn't find a free (and maybe open source) stickers API.

If there is one, someone can change the API or simply add another source for these stickers, so there will be an even wider range of sticker available, and not just mainly icons.

I still believe the iconify API is a good choice, as there are over 200,000 icons to choose from. If you know the link you provided has an API, feel free to make a pull request on those changes.

@fathisiddiqi
Copy link
Contributor

how about tenor api?

@enkei64
Copy link
Contributor Author

enkei64 commented Aug 15, 2025

how about tenor api?

Looks like it's only for GIFs

@fathisiddiqi
Copy link
Contributor

fathisiddiqi commented Aug 15, 2025

It also has stickers.

Screenshot 2025-08-15 at 16 01 06

but i don't know what the sticker means in this menu.
In my opinion, a sticker is decorative, not an icon.

you can check the sticker on Canva or CapCut itself, it could be like this

Screenshot 2025-08-15 at 15 57 23 Screenshot 2025-08-15 at 15 58 28

I think the menu will be misunderstood if there is a sticker that is different from the icon

@enkei64
Copy link
Contributor Author

enkei64 commented Aug 15, 2025

It also has stickers.

Screenshot 2025-08-15 at 16 01 06

but i don't know what the sticker means in this menu.

In my opinion, a sticker is decorative, not an icon.

you can check the sticker on Canva or CapCut itself, it could be like this

Screenshot 2025-08-15 at 15 57 23 Screenshot 2025-08-15 at 15 58 28

I think the menu will be misunderstood if there is a sticker that is different from the icon

Talking about the API, it looks like it only has GIFs.

@mazeincoding
Copy link
Collaborator

imo this is better then capcut's stickers

throughout my 3 years of video editing, i've needed "app icons" (eg, javascript logo, facebook) and all the other cool iconify stickers than all the stickers capcut has (i genuinely haven't seen anyone use those)

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.

3 participants