Skip to content

Conversation

@christian-byrne
Copy link
Contributor

@christian-byrne christian-byrne commented Jan 20, 2026

Summary

Consolidates duplicate image upload implementations into a shared service, eliminating ~60-70 LOC duplication. RFC here.

Changes

  • Add src/platform/assets/services/uploadService.ts with uploadMedia() and uploadMediaBatch()
  • Refactor Load3dUtils to use uploadService (eliminates 50+ LOC)
  • Refactor WidgetSelectDropdown to use uploadService (eliminates 20+ LOC)
  • Add comprehensive unit tests for uploadService
  • Maintain backward compatibility for all existing APIs

Benefits

  • Single source of truth for upload logic
  • Consistent error handling
  • Type-safe interfaces
  • Easier to test and maintain

┆Issue is synchronized with this Notion page by Unito

@christian-byrne christian-byrne requested a review from a team as a code owner January 20, 2026 20:38
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jan 20, 2026
@github-actions
Copy link

github-actions bot commented Jan 20, 2026

🎭 Playwright Tests: ✅ Passed

Results: 507 passed, 0 failed, 0 flaky, 8 skipped (Total: 515)

📊 Browser Reports
  • chromium: View Report (✅ 495 / ❌ 0 / ⚠️ 0 / ⏭️ 8)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 9 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@github-actions
Copy link

github-actions bot commented Jan 20, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 01/30/2026, 07:01:10 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Adds a centralized upload service (uploadMedia/uploadMediaBatch), refactors multiple upload sites to use it (Load3d utils and widget uploads), adds tests for the new service, minor template formatting, and guards backgroundImage assignment in useLoad3d.ts when upload path is falsy.

Changes

Cohort / File(s) Summary
New Upload Service
src/platform/assets/services/uploadService.ts
Adds uploadMedia and uploadMediaBatch, UploadResult type, conversion of inputs (File/Blob/dataURL), size validation, FormData construction, api.fetchApi POST handling, and normalized result shapes.
Upload Service Tests
src/platform/assets/services/uploadService.test.ts
Adds tests for File/Blob/dataURL uploads, FormData parameters, size limits, network/error handling, original_ref behavior, and batch uploads.
Refactored Load3d Upload
src/extensions/core/load3d/Load3dUtils.ts
Replaces inline upload logic with uploadMedia calls in uploadTempImage, uploadFile, and uploadMultipleFiles; uploadMultipleFiles now returns Promise<string[]> and filters out undefined paths; error handling/toasts adjusted.
Refactored Widget Upload
src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue
Replaces per-file upload logic with uploadMediaBatch-based uploads, collects uploaded paths, updates widget options/values, and surfaces per-file errors via toasts.
Background Image Guard
src/composables/useLoad3d.ts
Adds a truthy check on uploadedPath before assigning backgroundImage and calling setBackgroundImage in handleBackgroundImageUpdate.
Stylistic Template Change
src/platform/assets/components/MediaAssetFilterBar.vue
Template/attribute reflow and formatting only; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  participant Component as Component (Widget / Load3d)
  participant UploadSvc as UploadService
  participant API as api.fetchApi
  participant Store as AssetsStore
  participant UI as UI (Toast)

  Component->>UploadSvc: uploadMedia(input, config) / uploadMediaBatch(inputs, config)
  UploadSvc->>UploadSvc: convert input to File (File/Blob/dataURL)
  UploadSvc->>UploadSvc: validate file size (maxSizeMB)
  alt size OK
    UploadSvc->>API: POST FormData (image, subfolder, type, original_ref)
    API-->>UploadSvc: HTTP response (success or error)
    alt success
      UploadSvc-->>Component: { success: true, path, name }
      Component->>Store: update assets (if subfolder matches)
      Component->>UI: optional success UI / set value
    else api error
      UploadSvc-->>Component: { success: false, error, response? }
      Component->>UI: show toast(error or generic)
    end
  else size exceeds
    UploadSvc-->>Component: { success: false, error: "exceeds maximum" }
    Component->>UI: show toast(fileTooLarge)
  end
Loading

Suggested reviewers

  • shinshin86
  • PabloWiedemann
  • KarryCharon
  • DrJKL
  • comfydesigner

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.

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

Caution

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

⚠️ Outside diff range comments (1)
src/extensions/core/load3d/Load3dUtils.ts (1)

86-92: uploadMultipleFiles doesn't report partial failures.

The function calls uploadFile for each file and uses Promise.all, but uploadFile returns undefined on failure rather than throwing. This means some uploads could fail silently while others succeed, with no aggregated feedback to the caller.

Consider returning the results array or at least logging/reporting which files failed:

♻️ Suggested improvement
-  static async uploadMultipleFiles(files: FileList, subfolder: string = '3d') {
+  static async uploadMultipleFiles(files: FileList, subfolder: string = '3d'): Promise<string[]> {
     const uploadPromises = Array.from(files).map((file) =>
       this.uploadFile(file, subfolder)
     )
 
-    await Promise.all(uploadPromises)
+    const results = await Promise.all(uploadPromises)
+    return results.filter((path): path is string => path !== undefined)
   }
🤖 Fix all issues with AI agents
In `@src/platform/assets/services/uploadService.test.ts`:
- Around line 57-76: The test "uploads dataURL successfully" sets global.fetch
directly which can leak into other tests; update the test to restore the
global.fetch mock after the test (or replace the direct assignment with
vi.spyOn(global, 'fetch') so it can be auto-restored), and ensure cleanup by
calling vi.restoreAllMocks() or explicitly resetting global.fetch in afterEach/
finally; reference the test case name (it('uploads dataURL successfully'...)),
the global.fetch mock, and the uploadMedia call so the change is applied in that
test file.
- Around line 162-186: The test for uploadMediaBatch is fragile because it
returns the same mockResponse object for multiple fetchApi calls and relies on
json.mockResolvedValueOnce sequencing; fix by making api.fetchApi return
distinct response objects (or mockResolvedValueOnce with separate objects) so
each call has its own json mock resolution—update the test to mock
vi.mocked(api.fetchApi).mockResolvedValueOnce(response1).mockResolvedValueOnce(response2)
or create two mockResponse instances with their own json mocks and have fetchApi
return them, ensuring uploadMediaBatch sees separate responses for each File.

In `@src/platform/assets/services/uploadService.ts`:
- Around line 23-30: The UploadResult interface currently uses response: any;
replace that with a safer type (preferably a defined API response interface like
UploadApiResponse or at minimum response: unknown) in the UploadResult
declaration so callers must explicitly validate/cast the shape; update any
functions that construct or read UploadResult (e.g., places returning
UploadResult or reading .response) to either map the API payload into the new
UploadApiResponse type or perform type guards before accessing fields, and
add/export the new UploadApiResponse type next to UploadResult to keep typings
consistent across the module.
- Around line 46-51: The code currently assumes source is a valid data URL and
calls fetch(source) which can throw for malformed strings; update the upload
logic around the blob creation (the fetch(source) call and subsequent new
File([...]) using source, filename, mimeType) to first validate source (e.g.,
ensure typeof source === 'string' and it matches a data URL pattern such as
starting with "data:") and then wrap the fetch(...) in a try/catch; on error,
throw or return a descriptive error message that includes the invalid source
context so callers can handle malformed input instead of crashing at runtime.
- Around line 17-21: Remove the local ImageRef interface declaration in
uploadService.ts and instead import the exported ImageRef type from
src/stores/maskEditorDataStore.ts; update the top-level imports to include
ImageRef and delete the redundant local interface definition. Ensure usages in
this file (e.g., any references to originalRef or functions that accept
ImageRef) rely on the imported type and that the optional subfolder and type
fields are handled consistently with the store's definition. Confirm there are
no other duplicate type declarations introduced by this change.

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jan 20, 2026
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

🤖 Fix all issues with AI agents
In `@src/extensions/core/load3d/Load3dUtils.ts`:
- Around line 12-24: Replace the hard-coded temp-upload error string in the temp
upload path of Load3dUtils (the block that calls uploadMedia and checks
result.success) with a localized message using
t('toastMessages.tempUploadFailed', { error: result.error || '' }) and pass that
localized string to useToastStore().addAlert and the thrown Error; follow the
same pattern used in uploadFile() for consistency. Also add the key
"toastMessages.tempUploadFailed" to src/locales/en/main.json with an appropriate
English message that accepts an {error} interpolation. Ensure you import or have
access to the t function in Load3dUtils if not already available.

In `@src/platform/assets/services/uploadService.test.ts`:
- Around line 21-30: Replace the seven occurrences of the unsafe "as any" cast
in this test file by casting the mock fetch Response objects to
"Partial<Response> as Response" so the mocks remain type-safe; locate the
mockResponse objects and the vi.mocked(api.fetchApi).mockResolvedValue(...)
calls (e.g., the mockResponse used in uploadService tests) and change each mock
object cast from "as any" to "as Partial<Response> as Response", and do the same
pattern for other similar mocked responses in this file.

In `@src/platform/assets/services/uploadService.ts`:
- Around line 52-55: The error thrown when a Data URL fails validation in
uploadService.ts currently echoes part of the input (source.substring...), which
can leak user data; update the failure path in the validation block that calls
isDataURL(source) to throw a generic error message (e.g., "Invalid data URL")
without including any portion of the source string, and ensure any callers of
this validation (the upload handler in the same service or functions that
reference source) continue to receive and handle the generic error.
- Around line 18-23: Add runtime validation for the API responses by creating a
Zod schema that matches the UploadApiResponse interface (e.g., const
uploadApiResponseSchema = z.object({ name: z.string(), subfolder:
z.string().optional(), type: z.string().optional() })), then replace raw
resp.json() parsing in uploadService with schema.safeParse(respJson) where
respJson is the parsed JSON; if safeParse fails, return the failure state
(success: false or equivalent) and log/propagate the error consistent with
assetService.ts pattern (use the same error handling as in assetService.ts lines
484-491). Apply this at both places where resp.json() is used in this file so
validated data is returned when safeParse succeeds and failure is returned when
it doesn't.

Comment on lines 12 to 52
const filename = `${prefix}_${Date.now()}.${fileType}`
const result = await uploadMedia(
{ source: imageData, filename },
{ subfolder: 'threed', type: 'temp' }
)

if (resp.status !== 200) {
const err = `Error uploading temp file: ${resp.status} - ${resp.statusText}`
if (!result.success) {
const err = `Error uploading temp file: ${result.error}`
useToastStore().addAlert(err)
throw new Error(err)
}

return await resp.json()
return result.response
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and check its contents
fd "Load3dUtils.ts" --type f

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 111


🏁 Script executed:

# Check the structure of src directory
git ls-files src/ | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1127


🏁 Script executed:

# Look for useToastStore to understand if it displays user-facing messages
rg "useToastStore" -t ts -t vue -A 2 --max-count 5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

# Check how error messages are handled in similar upload functions
rg "uploadMedia\|uploadFile" -t ts -A 5 | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Check existing i18n patterns for errors
rg "toastMessages\|toast" src/locales/ -t json | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Read the file to verify the code snippet
cat -n src/extensions/core/load3d/Load3dUtils.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3399


🏁 Script executed:

# Find useToastStore definition
rg "useToastStore" -t ts --no-heading -B 2 -A 5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 50382


🏁 Script executed:

# Check locales structure
cat src/locales/en/main.json | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3898


🏁 Script executed:

# Search for similar toast/alert error patterns
rg "addAlert|useToastStore" --type-list

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 459


🏁 Script executed:

# Check for toastMessages section in locales
rg "toastMessages" src/locales/en/main.json -A 5 | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 363


🏁 Script executed:

# Look for existing upload-related error keys
rg "upload|Upload" src/locales/en/main.json

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1845


🏁 Script executed:

# Check the audioService.ts to see similar pattern
grep -n "Error uploading temp file" src/services/audioService.ts -B 2 -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 262


Localize the temp-upload error message.

The error string on line 19 is user-facing and should be localized. Use t('toastMessages.tempUploadFailed', { error: result.error || '' }) and add the corresponding key to src/locales/en/main.json. This matches the pattern used in uploadFile() (lines 38, 51) and aligns with the coding guideline that error messages must be user-friendly and actionable.

🤖 Prompt for AI Agents
In `@src/extensions/core/load3d/Load3dUtils.ts` around lines 12 - 24, Replace the
hard-coded temp-upload error string in the temp upload path of Load3dUtils (the
block that calls uploadMedia and checks result.success) with a localized message
using t('toastMessages.tempUploadFailed', { error: result.error || '' }) and
pass that localized string to useToastStore().addAlert and the thrown Error;
follow the same pattern used in uploadFile() for consistency. Also add the key
"toastMessages.tempUploadFailed" to src/locales/en/main.json with an appropriate
English message that accepts an {error} interpolation. Ensure you import or have
access to the t function in Load3dUtils if not already available.

Comment on lines 21 to 32
const mockResponse = {
status: 200,
json: vi.fn().mockResolvedValue({
name: 'test.png',
subfolder: 'uploads'
})
}

vi.mocked(api.fetchApi).mockResolvedValue(mockResponse as any)

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the test file
find . -name "uploadService.test.ts" -type f

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 196


🏁 Script executed:

# Read the file with line numbers to see the context
if [ -f "src/platform/assets/services/uploadService.test.ts" ]; then
  wc -l "src/platform/assets/services/uploadService.test.ts"
fi

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 124


🏁 Script executed:

# Get the specific lines mentioned (21-30) with context
if [ -f "src/platform/assets/services/uploadService.test.ts" ]; then
  sed -n '1,40p' "src/platform/assets/services/uploadService.test.ts" | cat -n
fi

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1446


🏁 Script executed:

# Search for all 'as any' occurrences in the file
if [ -f "src/platform/assets/services/uploadService.test.ts" ]; then
  rg "as any" "src/platform/assets/services/uploadService.test.ts" -A 2 -B 2
fi

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 993


🏁 Script executed:

# Check what Response type is imported or used in the file
if [ -f "src/platform/assets/services/uploadService.test.ts" ]; then
  head -30 "src/platform/assets/services/uploadService.test.ts"
fi

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 797


🏁 Script executed:

# Read more of the file to see all imports
head -50 "src/platform/assets/services/uploadService.test.ts"

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1406


🏁 Script executed:

# Check the uploadService.ts file to understand return types
cat -n "src/platform/assets/services/uploadService.ts" | head -60

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1877


🏁 Script executed:

# Check what api.fetchApi's type signature is
find . -path "*/scripts/api*" -name "*.ts" -o -name "*.js" | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 421


🏁 Script executed:

# Search for other test patterns with mocked responses in similar test files
rg "mockResolvedValue" "src/**/*.test.ts" -B 5 -A 1 | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 126


🏁 Script executed:

# Check the api.ts file to see fetchApi signature
head -100 "src/scripts/api.ts" | cat -n

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3601


🏁 Script executed:

# Search for fetchApi definition and its return type
rg "fetchApi" "src/scripts/api.ts" -A 10 | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1658


🏁 Script executed:

# Look for other mock patterns in test files
rg "mockResolvedValue" "src" -A 2 -B 2 | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 8346


🏁 Script executed:

# Check if Response type is used anywhere or if a custom type is defined
rg "interface.*Response|type.*Response" "src/platform/assets/services" -A 5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 430


Replace as any casts in mock responses throughout the test file.

as any violates the project's type-safety guidance. Use as Partial<Response> as Response instead to explicitly acknowledge the incomplete implementation while maintaining type safety. There are 7 occurrences of this pattern in the file that should be updated.

🧩 Example fix
      const mockResponse = {
        status: 200,
        json: vi.fn().mockResolvedValue({
          name: 'test.png',
          subfolder: 'uploads'
        })
      }

-      vi.mocked(api.fetchApi).mockResolvedValue(mockResponse as any)
+      vi.mocked(api.fetchApi).mockResolvedValue(
+        mockResponse as Partial<Response> as Response
+      )
🤖 Prompt for AI Agents
In `@src/platform/assets/services/uploadService.test.ts` around lines 21 - 30,
Replace the seven occurrences of the unsafe "as any" cast in this test file by
casting the mock fetch Response objects to "Partial<Response> as Response" so
the mocks remain type-safe; locate the mockResponse objects and the
vi.mocked(api.fetchApi).mockResolvedValue(...) calls (e.g., the mockResponse
used in uploadService tests) and change each mock object cast from "as any" to
"as Partial<Response> as Response", and do the same pattern for other similar
mocked responses in this file.

Comment on lines +18 to +23
interface UploadApiResponse {
name: string
subfolder?: string
type?: string
}

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 | 🟠 Major

Add runtime validation to uploadService using safeParse for API responses.

The UploadApiResponse interface at lines 18-23 defines the expected shape, but resp.json() is not validated at runtime. Unexpected payloads will bypass the interface and still return success: true, propagating invalid paths. Add a Zod schema with safeParse() validation (following the pattern in assetService.ts lines 484-491) and return a failure state when validation fails.

Implementation pattern
+import { z } from 'zod'
+
+const uploadApiResponseSchema = z.object({
+  name: z.string(),
+  subfolder: z.string().optional(),
+  type: z.string().optional()
+})
...
-    const data: UploadApiResponse = await resp.json()
+    const parsed = uploadApiResponseSchema.safeParse(await resp.json())
+    if (!parsed.success) {
+      return {
+        success: false,
+        path: '',
+        name: '',
+        subfolder: '',
+        error: 'Invalid upload response',
+        response: null
+      }
+    }
+    const data: UploadApiResponse = parsed.data

Apply this validation at both locations where responses are parsed (lines 18-23 region and lines 128-137).

🤖 Prompt for AI Agents
In `@src/platform/assets/services/uploadService.ts` around lines 18 - 23, Add
runtime validation for the API responses by creating a Zod schema that matches
the UploadApiResponse interface (e.g., const uploadApiResponseSchema =
z.object({ name: z.string(), subfolder: z.string().optional(), type:
z.string().optional() })), then replace raw resp.json() parsing in uploadService
with schema.safeParse(respJson) where respJson is the parsed JSON; if safeParse
fails, return the failure state (success: false or equivalent) and log/propagate
the error consistent with assetService.ts pattern (use the same error handling
as in assetService.ts lines 484-491). Apply this at both places where
resp.json() is used in this file so validated data is returned when safeParse
succeeds and failure is returned when it doesn't.

Comment on lines 52 to 55
// dataURL string
if (!isDataURL(source)) {
throw new Error(`Invalid data URL: ${source.substring(0, 50)}...`)
}
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 | 🟠 Major

Avoid echoing data URL contents in error messages.

Including a substring of the data URL can leak user content into logs/toasts; keep this generic.

🔒 Suggested fix
-  if (!isDataURL(source)) {
-    throw new Error(`Invalid data URL: ${source.substring(0, 50)}...`)
-  }
+  if (!isDataURL(source)) {
+    throw new Error('Invalid data URL')
+  }
📝 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
// dataURL string
if (!isDataURL(source)) {
throw new Error(`Invalid data URL: ${source.substring(0, 50)}...`)
}
// dataURL string
if (!isDataURL(source)) {
throw new Error('Invalid data URL')
}
🤖 Prompt for AI Agents
In `@src/platform/assets/services/uploadService.ts` around lines 52 - 55, The
error thrown when a Data URL fails validation in uploadService.ts currently
echoes part of the input (source.substring...), which can leak user data; update
the failure path in the validation block that calls isDataURL(source) to throw a
generic error message (e.g., "Invalid data URL") without including any portion
of the source string, and ensure any callers of this validation (the upload
handler in the same service or functions that reference source) continue to
receive and handle the generic error.

@christian-byrne christian-byrne force-pushed the refactor/consolidate-image-upload branch from 48ca2b8 to 5fc3cb6 Compare January 21, 2026 05:25
@github-actions
Copy link

github-actions bot commented Jan 21, 2026

Bundle Size Report

Summary

  • Raw size: 22.1 MB baseline 22.1 MB — 🔴 +1.57 kB
  • Gzip: 4.61 MB baseline 4.61 MB — 🔴 +474 B
  • Brotli: 3.42 MB baseline 3.42 MB — 🔴 +551 B
  • Bundles: 173 current • 173 baseline • 84 added / 84 removed

Category Glance
Data & Services 🔴 +1.99 kB (2.71 MB) · Other 🟢 -407 B (7.1 MB) · Panels & Settings 🟢 -8 B (471 kB) · Vendor & Third-Party ⚪ 0 B (10.7 MB) · Graph Workspace ⚪ 0 B (974 kB) · Views & Navigation ⚪ 0 B (80.7 kB) · + 5 more

Per-category breakdown
App Entry Points — 26 kB (baseline 26 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-0o8eSuaX.js (removed) 26 kB 🟢 -26 kB 🟢 -7.5 kB 🟢 -6.61 kB
assets/index-BRd0-N73.js (new) 26 kB 🔴 +26 kB 🔴 +7.51 kB 🔴 +6.61 kB

Status: 1 added / 1 removed

Graph Workspace — 974 kB (baseline 974 kB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BbQqs2Ly.js (removed) 974 kB 🟢 -974 kB 🟢 -197 kB 🟢 -149 kB
assets/GraphView-DbVBkmt9.js (new) 974 kB 🔴 +974 kB 🔴 +197 kB 🔴 +149 kB

Status: 1 added / 1 removed

Views & Navigation — 80.7 kB (baseline 80.7 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-B8LkFGIm.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -3.61 kB 🟢 -3.05 kB
assets/CloudSurveyView-CDt3x79t.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +3.6 kB 🔴 +3.05 kB
assets/CloudLoginView-C9R0_Ugu.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.09 kB 🟢 -2.71 kB
assets/CloudLoginView-Dy3uss5O.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.09 kB 🔴 +2.72 kB
assets/UserCheckView-BHUD8MXU.js (new) 10.5 kB 🔴 +10.5 kB 🔴 +2.45 kB 🔴 +2.13 kB
assets/UserCheckView-CLwAHgYQ.js (removed) 10.5 kB 🟢 -10.5 kB 🟢 -2.44 kB 🟢 -2.13 kB
assets/CloudLayoutView-BB2Oum5f.js (removed) 8.54 kB 🟢 -8.54 kB 🟢 -2.24 kB 🟢 -1.96 kB
assets/CloudLayoutView-CqLS3pGe.js (new) 8.54 kB 🔴 +8.54 kB 🔴 +2.24 kB 🔴 +1.96 kB
assets/CloudSignupView-B3qZgE2h.js (removed) 8.18 kB 🟢 -8.18 kB 🟢 -2.32 kB 🟢 -2.02 kB
assets/CloudSignupView-CmwPcaOn.js (new) 8.18 kB 🔴 +8.18 kB 🔴 +2.32 kB 🔴 +2.02 kB
assets/CloudForgotPasswordView-B1msQ3bg.js (removed) 6.26 kB 🟢 -6.26 kB 🟢 -1.93 kB 🟢 -1.69 kB
assets/CloudForgotPasswordView-TXQhs3zH.js (new) 6.26 kB 🔴 +6.26 kB 🔴 +1.92 kB 🔴 +1.69 kB
assets/UserSelectView-2CmYHev-.js (new) 5.28 kB 🔴 +5.28 kB 🔴 +1.76 kB 🔴 +1.57 kB
assets/UserSelectView-D0PLhTti.js (removed) 5.28 kB 🟢 -5.28 kB 🟢 -1.76 kB 🟢 -1.57 kB
assets/CloudSubscriptionRedirectView-DPadqUKi.js (removed) 5.27 kB 🟢 -5.27 kB 🟢 -1.73 kB 🟢 -1.54 kB
assets/CloudSubscriptionRedirectView-WbHpCkbM.js (new) 5.27 kB 🔴 +5.27 kB 🔴 +1.73 kB 🔴 +1.54 kB
assets/CloudAuthTimeoutView-BevAbWnL.js (removed) 5.24 kB 🟢 -5.24 kB 🟢 -1.7 kB 🟢 -1.48 kB
assets/CloudAuthTimeoutView-DnI73d1E.js (new) 5.24 kB 🔴 +5.24 kB 🔴 +1.7 kB 🔴 +1.49 kB
assets/CloudSorryContactSupportView-cm9oKn4s.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-CUzumK-h.js 500 B 500 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

Panels & Settings — 471 kB (baseline 471 kB) • 🟢 -8 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/WorkspacePanel-22RIfqld.js (removed) 29.8 kB 🟢 -29.8 kB 🟢 -5.89 kB 🟢 -5.14 kB
assets/WorkspacePanel-CkANFwOx.js (new) 29.8 kB 🔴 +29.8 kB 🔴 +5.89 kB 🔴 +5.14 kB
assets/LegacyCreditsPanel-DSHi1D4z.js (removed) 23.8 kB 🟢 -23.8 kB 🟢 -5.94 kB 🟢 -5.22 kB
assets/LegacyCreditsPanel-R08n4S4e.js (new) 23.8 kB 🔴 +23.8 kB 🔴 +5.95 kB 🔴 +5.23 kB
assets/SubscriptionPanel-DpViEyFm.js (removed) 21 kB 🟢 -21 kB 🟢 -5.04 kB 🟢 -4.44 kB
assets/SubscriptionPanel-DS69jBPS.js (new) 21 kB 🔴 +21 kB 🔴 +5.04 kB 🔴 +4.45 kB
assets/KeybindingPanel-bnJV0gT5.js (new) 14.3 kB 🔴 +14.3 kB 🔴 +3.76 kB 🔴 +3.34 kB
assets/KeybindingPanel-Cqh2zhF5.js (removed) 14.3 kB 🟢 -14.3 kB 🟢 -3.76 kB 🟢 -3.34 kB
assets/AboutPanel-Dn_tLd0l.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.68 kB 🔴 +2.43 kB
assets/AboutPanel-nAPeX13O.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.68 kB 🟢 -2.43 kB
assets/ExtensionPanel-BN17h4C0.js (removed) 10.2 kB 🟢 -10.2 kB 🟢 -2.71 kB 🟢 -2.4 kB
assets/ExtensionPanel-Doo5eSsk.js (new) 10.2 kB 🔴 +10.2 kB 🔴 +2.71 kB 🔴 +2.4 kB
assets/ServerConfigPanel-BChPlrSC.js (new) 7.23 kB 🔴 +7.23 kB 🔴 +2.16 kB 🔴 +1.94 kB
assets/ServerConfigPanel-Drtk3Cj9.js (removed) 7.23 kB 🟢 -7.23 kB 🟢 -2.17 kB 🟢 -1.94 kB
assets/UserPanel-C94GhepI.js (new) 6.58 kB 🔴 +6.58 kB 🔴 +1.91 kB 🔴 +1.67 kB
assets/UserPanel-CX_ZP5HA.js (removed) 6.58 kB 🟢 -6.58 kB 🟢 -1.91 kB 🟢 -1.68 kB
assets/refreshRemoteConfig-BgxTz2MO.js (new) 1.31 kB 🔴 +1.31 kB 🔴 +574 B 🔴 +496 B
assets/refreshRemoteConfig-BvAnKw7S.js (removed) 1.31 kB 🟢 -1.31 kB 🟢 -571 B 🟢 -496 B
assets/config-CQmXnrXP.js (removed) 1.16 kB 🟢 -1.16 kB 🟢 -609 B 🟢 -539 B
assets/config-Bn0UvDMc.js (new) 1.15 kB 🔴 +1.15 kB 🔴 +604 B 🔴 +528 B
assets/cloudRemoteConfig-BpfcOE1q.js (removed) 1.11 kB 🟢 -1.11 kB 🟢 -509 B 🟢 -439 B
assets/cloudRemoteConfig-CGM5tHP9.js (new) 1.11 kB 🔴 +1.11 kB 🔴 +511 B 🔴 +454 B
assets/refreshRemoteConfig-Bckc2EMm.js (removed) 169 B 🟢 -169 B 🟢 -108 B 🟢 -102 B
assets/refreshRemoteConfig-D9LTv7dQ.js (new) 169 B 🔴 +169 B 🔴 +108 B 🔴 +105 B
assets/remoteConfig-B0mlVvm7.js 788 B 788 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-2UNjEj6k.js 32.9 kB 32.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B2OMGvh7.js 31.2 kB 31.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BcujOfpn.js 29.6 kB 29.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BI09_t23.js 29.4 kB 29.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BKamuseh.js 25.8 kB 25.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BlTun9tZ.js 26.4 kB 26.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CZ62uO3e.js 30.2 kB 30.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DaK-NByz.js 35.2 kB 35.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DaS3cSXp.js 39.4 kB 39.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DWbMuaAa.js 32 kB 32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-S7pA60Hj.js 30.4 kB 30.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 12 added / 12 removed

User & Accounts — 3.94 kB (baseline 3.94 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-D3luOwL2.js (removed) 3.54 kB 🟢 -3.54 kB 🟢 -1.24 kB 🟢 -1.07 kB
assets/auth-DvQgTksh.js (new) 3.54 kB 🔴 +3.54 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/firebaseAuthStore-HunZJeIy.js (new) 217 B 🔴 +217 B 🔴 +136 B 🔴 +118 B
assets/firebaseAuthStore-vCBMphFP.js (removed) 217 B 🟢 -217 B 🟢 -138 B 🟢 -121 B
assets/auth-BPMO3atV.js (removed) 178 B 🟢 -178 B 🟢 -142 B 🟢 -143 B
assets/auth-DpTIwA00.js (new) 178 B 🔴 +178 B 🔴 +142 B 🔴 +132 B

Status: 3 added / 3 removed

Editors & Dialogs — 2.89 kB (baseline 2.89 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-C3ZkHuZL.js (new) 2.71 kB 🔴 +2.71 kB 🔴 +1.28 kB 🔴 +1.14 kB
assets/useSubscriptionDialog-nhvd3Qi8.js (removed) 2.71 kB 🟢 -2.71 kB 🟢 -1.28 kB 🟢 -1.15 kB
assets/useSubscriptionDialog-1nPIsiTW.js (removed) 179 B 🟢 -179 B 🟢 -110 B 🟢 -101 B
assets/useSubscriptionDialog-6l8OgC2R.js (new) 179 B 🔴 +179 B 🔴 +110 B 🔴 +103 B

Status: 2 added / 2 removed

UI Components — 33.7 kB (baseline 33.7 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-B3NQP4hW.js (new) 9.52 kB 🔴 +9.52 kB 🔴 +2.69 kB 🔴 +2.42 kB
assets/ComfyQueueButton-B7AzI4p_.js (removed) 9.52 kB 🟢 -9.52 kB 🟢 -2.69 kB 🟢 -2.42 kB
assets/SubscribeButton-CgElFdpH.js (removed) 4.63 kB 🟢 -4.63 kB 🟢 -1.57 kB 🟢 -1.39 kB
assets/SubscribeButton-HyKeWDBu.js (new) 4.63 kB 🔴 +4.63 kB 🔴 +1.57 kB 🔴 +1.39 kB
assets/CloudBadge-CvD-s9oR.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +719 B 🔴 +647 B
assets/CloudBadge-DdpkQvvD.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -721 B 🟢 -644 B
assets/cloudFeedbackTopbarButton-BFQoPxBi.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -675 B 🟢 -573 B
assets/cloudFeedbackTopbarButton-BwN24stc.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +673 B 🔴 +573 B
assets/ComfyQueueButton-BPi-eK-R.js (new) 181 B 🔴 +181 B 🔴 +118 B 🔴 +109 B
assets/ComfyQueueButton-DhH2bjjk.js (removed) 181 B 🟢 -181 B 🟢 -118 B 🟢 -124 B
assets/Button-DbRyW27H.js 3.82 kB 3.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-BFw4kSAY.js 8.36 kB 8.36 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-D80lITos.js 1.73 kB 1.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-CVau1vM3.js 2.41 kB 2.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 2.71 MB (baseline 2.71 MB) • 🔴 +1.99 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-IyBB2e61.js (new) 2.01 MB 🔴 +2.01 MB 🔴 +425 kB 🔴 +325 kB
assets/dialogService-C0jZxvrW.js (removed) 2.01 MB 🟢 -2.01 MB 🟢 -425 kB 🟢 -324 kB
assets/api-BBUlbYlQ.js (new) 675 kB 🔴 +675 kB 🔴 +149 kB 🔴 +119 kB
assets/api-0_xJMVsr.js (removed) 675 kB 🟢 -675 kB 🟢 -149 kB 🟢 -119 kB
assets/releaseStore-C4ZKKz9O.js (new) 8.91 kB 🔴 +8.91 kB 🔴 +2.4 kB 🔴 +2.12 kB
assets/releaseStore-Msq5KIWQ.js (removed) 8.91 kB 🟢 -8.91 kB 🟢 -2.4 kB 🟢 -2.12 kB
assets/keybindingService-DaoPdvvj.js (new) 6.74 kB 🔴 +6.74 kB 🔴 +1.76 kB 🔴 +1.52 kB
assets/keybindingService-DyZNHzol.js (removed) 6.74 kB 🟢 -6.74 kB 🟢 -1.75 kB 🟢 -1.53 kB
assets/bootstrapStore-CT9MfHnC.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.03 kB 🟢 -967 B
assets/bootstrapStore-zTYnoLKw.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.03 kB 🔴 +961 B
assets/userStore-Bbs7yE1D.js (removed) 2.16 kB 🟢 -2.16 kB 🟢 -811 B 🟢 -724 B
assets/userStore-CQoKM0uX.js (new) 2.16 kB 🔴 +2.16 kB 🔴 +811 B 🔴 +725 B
assets/audioService-BzwHlK45.js (new) 2.03 kB 🔴 +2.03 kB 🔴 +930 B 🔴 +810 B
assets/audioService-C4ihUw-I.js (removed) 2.03 kB 🟢 -2.03 kB 🟢 -929 B 🟢 -808 B
assets/releaseStore-PhTp9Hcu.js (new) 140 B 🔴 +140 B 🔴 +106 B 🔴 +110 B
assets/releaseStore-RM6-Y5p0.js (removed) 140 B 🟢 -140 B 🟢 -106 B 🟢 -110 B
assets/serverConfigStore-DOoqLe5c.js 2.64 kB 2.64 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 8 added / 8 removed

Utilities & Hooks — 25.3 kB (baseline 25.3 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useErrorHandling-BJ4-7oLQ.js (removed) 5.21 kB 🟢 -5.21 kB 🟢 -1.53 kB 🟢 -1.34 kB
assets/useErrorHandling-DUouVBj1.js (new) 5.21 kB 🔴 +5.21 kB 🔴 +1.53 kB 🔴 +1.35 kB
assets/useWorkspaceUI-D0xn5UN-.js (new) 3.42 kB 🔴 +3.42 kB 🔴 +975 B 🔴 +839 B
assets/useWorkspaceUI-u-o6kaJ8.js (removed) 3.42 kB 🟢 -3.42 kB 🟢 -974 B 🟢 -843 B
assets/useSubscriptionActions-C_-bV1aq.js (removed) 2.22 kB 🟢 -2.22 kB 🟢 -867 B 🟢 -763 B
assets/useSubscriptionActions-DH9Xuahi.js (new) 2.22 kB 🔴 +2.22 kB 🔴 +870 B 🔴 +764 B
assets/subscriptionCheckoutUtil-CDQ8THBd.js (removed) 2.03 kB 🟢 -2.03 kB 🟢 -871 B 🟢 -771 B
assets/subscriptionCheckoutUtil-DHbrSrq2.js (new) 2.03 kB 🔴 +2.03 kB 🔴 +873 B 🔴 +769 B
assets/useSubscriptionCredits-Chj6K3LE.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -600 B 🟢 -529 B
assets/useSubscriptionCredits-Cz3eusDN.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +597 B 🔴 +526 B
assets/audioUtils-CJEpTYmi.js (removed) 970 B 🟢 -970 B 🟢 -547 B 🟢 -459 B
assets/audioUtils-D9sCvU5l.js (new) 970 B 🔴 +970 B 🔴 +547 B 🔴 +458 B
assets/useCurrentUser-_XKMqbeH.js (removed) 145 B 🟢 -145 B 🟢 -114 B 🟢 -104 B
assets/useCurrentUser-e5_si9MS.js (new) 145 B 🔴 +145 B 🔴 +114 B 🔴 +108 B
assets/_plugin-vue_export-helper-DuK_Fly3.js 467 B 467 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-DfMUHmsF.js 7.2 kB 7.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-DM9z_tTX.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/tailwindUtil-BWBAZ7f9.js 488 B 488 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 7 added / 7 removed

Vendor & Third-Party — 10.7 MB (baseline 10.7 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-chart-DHGfk3hn.js 408 kB 408 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-B3TsI6ya.js 4.1 MB 4.1 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-4Jj8eU28.js 3.04 MB 3.04 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-aCG649nF.js 263 kB 263 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-CERwhPwK.js 1.83 MB 1.83 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-BxrEVL6s.js 650 kB 650 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-Dwii0E-t.js 13.6 kB 13.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-IX6P8SWv.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 7.1 MB (baseline 7.1 MB) • 🟢 -407 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-BBnvY8bq.js (new) 500 kB 🔴 +500 kB 🔴 +96.6 kB 🔴 +71.9 kB
assets/i18n-DKbrI9yU.js (removed) 500 kB 🟢 -500 kB 🟢 -96.5 kB 🟢 -71.8 kB
assets/core-Bpml2FxC.js (removed) 180 kB 🟢 -180 kB 🟢 -43.3 kB 🟢 -36.2 kB
assets/core-BzkN60_C.js (new) 180 kB 🔴 +180 kB 🔴 +43.3 kB 🔴 +36.2 kB
assets/WidgetSelect-CAtMnQpb.js (removed) 52.2 kB 🟢 -52.2 kB 🟢 -11.5 kB 🟢 -10 kB
assets/WidgetSelect-BIyW4Z3j.js (new) 51.9 kB 🔴 +51.9 kB 🔴 +11.4 kB 🔴 +9.9 kB
assets/Load3DControls-CAe9IHUk.js (new) 35.9 kB 🔴 +35.9 kB 🔴 +5.87 kB 🔴 +5.09 kB
assets/Load3DControls-visAYFcK.js (removed) 35.9 kB 🟢 -35.9 kB 🟢 -5.87 kB 🟢 -5.09 kB
assets/SubscriptionRequiredDialogContent-BqS6c9xu.js (new) 28.7 kB 🔴 +28.7 kB 🔴 +6.79 kB 🔴 +5.91 kB
assets/SubscriptionRequiredDialogContent-NC2Wm-qi.js (removed) 28.7 kB 🟢 -28.7 kB 🟢 -6.79 kB 🟢 -5.92 kB
assets/CurrentUserPopoverWorkspace-D_RbsXNh.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +4.99 kB 🔴 +4.43 kB
assets/CurrentUserPopoverWorkspace-IK21Y1TX.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -4.99 kB 🟢 -4.42 kB
assets/Load3D-Bb3LNhLj.js (new) 19.2 kB 🔴 +19.2 kB 🔴 +4.37 kB 🔴 +3.85 kB
assets/Load3D-DM6Rqpu-.js (removed) 19.2 kB 🟢 -19.2 kB 🟢 -4.38 kB 🟢 -3.84 kB
assets/WidgetInputNumber-03bMqGmh.js (new) 18.3 kB 🔴 +18.3 kB 🔴 +4.53 kB 🔴 +4.02 kB
assets/WidgetInputNumber-C1zzD6EW.js (removed) 18.3 kB 🟢 -18.3 kB 🟢 -4.53 kB 🟢 -4.03 kB
assets/WidgetRecordAudio-BKYjAkUn.js (new) 18.3 kB 🔴 +18.3 kB 🔴 +4.97 kB 🔴 +4.44 kB
assets/WidgetRecordAudio-CNAWyAke.js (removed) 18.3 kB 🟢 -18.3 kB 🟢 -4.97 kB 🟢 -4.44 kB
assets/SubscriptionPanelContentWorkspace-D_0MeVoR.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.47 kB 🟢 -3.9 kB
assets/SubscriptionPanelContentWorkspace-DKf_o3TX.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.47 kB 🔴 +3.9 kB
assets/WidgetImageCrop-2Hx-b5Be.js (new) 17.1 kB 🔴 +17.1 kB 🔴 +4.13 kB 🔴 +3.62 kB
assets/WidgetImageCrop-B8vN0f5F.js (removed) 17.1 kB 🟢 -17.1 kB 🟢 -4.14 kB 🟢 -3.63 kB
assets/PanelTemplate-2JgpsoeK.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -5.45 kB 🟢 -4.8 kB
assets/PanelTemplate-G9uj_P_k.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +5.45 kB 🔴 +4.8 kB
assets/AudioPreviewPlayer--JgveL1z.js (new) 10.8 kB 🔴 +10.8 kB 🔴 +2.97 kB 🔴 +2.66 kB
assets/AudioPreviewPlayer-Brfeorqe.js (removed) 10.8 kB 🟢 -10.8 kB 🟢 -2.97 kB 🟢 -2.65 kB
assets/InviteMemberDialogContent-Cns2g9Rm.js (removed) 8.36 kB 🟢 -8.36 kB 🟢 -2.5 kB 🟢 -2.17 kB
assets/InviteMemberDialogContent-ZZn9Sd6M.js (new) 8.36 kB 🔴 +8.36 kB 🔴 +2.51 kB 🔴 +2.16 kB
assets/WidgetWithControl-BhNk_cqm.js (removed) 8.07 kB 🟢 -8.07 kB 🟢 -2.68 kB 🟢 -2.41 kB
assets/WidgetWithControl-CBe5iCT2.js (new) 8.07 kB 🔴 +8.07 kB 🔴 +2.68 kB 🔴 +2.41 kB
assets/CreateWorkspaceDialogContent-B80fMO_Z.js (new) 5.93 kB 🔴 +5.93 kB 🔴 +1.93 kB 🔴 +1.68 kB
assets/CreateWorkspaceDialogContent-Bg3B71vi.js (removed) 5.93 kB 🟢 -5.93 kB 🟢 -1.92 kB 🟢 -1.68 kB
assets/EditWorkspaceDialogContent-a8rj2CI4.js (new) 5.7 kB 🔴 +5.7 kB 🔴 +1.88 kB 🔴 +1.64 kB
assets/EditWorkspaceDialogContent-BUzR5x5W.js (removed) 5.7 kB 🟢 -5.7 kB 🟢 -1.88 kB 🟢 -1.65 kB
assets/ValueControlPopover-B6DVR-t-.js (new) 5.17 kB 🔴 +5.17 kB 🔴 +1.69 kB 🔴 +1.5 kB
assets/ValueControlPopover-DdL0wMpV.js (removed) 5.17 kB 🟢 -5.17 kB 🟢 -1.69 kB 🟢 -1.5 kB
assets/DeleteWorkspaceDialogContent-Cd8ZAhh-.js (new) 4.59 kB 🔴 +4.59 kB 🔴 +1.56 kB 🔴 +1.35 kB
assets/DeleteWorkspaceDialogContent-Do8LHd_w.js (removed) 4.59 kB 🟢 -4.59 kB 🟢 -1.56 kB 🟢 -1.35 kB
assets/LeaveWorkspaceDialogContent-BL3WW07K.js (new) 4.41 kB 🔴 +4.41 kB 🔴 +1.51 kB 🔴 +1.31 kB
assets/LeaveWorkspaceDialogContent-C5ayjfdf.js (removed) 4.41 kB 🟢 -4.41 kB 🟢 -1.5 kB 🟢 -1.31 kB
assets/RemoveMemberDialogContent-BhN6FIWK.js (new) 4.38 kB 🔴 +4.38 kB 🔴 +1.45 kB 🔴 +1.27 kB
assets/RemoveMemberDialogContent-jR-phpTt.js (removed) 4.38 kB 🟢 -4.38 kB 🟢 -1.45 kB 🟢 -1.27 kB
assets/RevokeInviteDialogContent-B_NRigYz.js (removed) 4.29 kB 🟢 -4.29 kB 🟢 -1.47 kB 🟢 -1.29 kB
assets/RevokeInviteDialogContent-DiWG0J29.js (new) 4.29 kB 🔴 +4.29 kB 🔴 +1.47 kB 🔴 +1.29 kB
assets/GlobalToast-Bfq1l41B.js (removed) 3.05 kB 🟢 -3.05 kB 🟢 -1.1 kB 🟢 -941 B
assets/GlobalToast-D2TaB1Wj.js (new) 3.05 kB 🔴 +3.05 kB 🔴 +1.1 kB 🔴 +941 B
assets/SubscribeToRun-BNX1ZG14.js (removed) 2.96 kB 🟢 -2.96 kB 🟢 -1.15 kB 🟢 -1.01 kB
assets/SubscribeToRun-D34lQe2q.js (new) 2.96 kB 🔴 +2.96 kB 🔴 +1.16 kB 🔴 +1.01 kB
assets/cloudSessionCookie-CNH0jLBl.js (new) 2.94 kB 🔴 +2.94 kB 🔴 +928 B 🔴 +796 B
assets/cloudSessionCookie-DwHfnsk8.js (removed) 2.94 kB 🟢 -2.94 kB 🟢 -930 B 🟢 -800 B
assets/BaseViewTemplate-B2Ree9mF.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.04 kB 🟢 -941 B
assets/BaseViewTemplate-Bs44k70y.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.04 kB 🔴 +942 B
assets/CloudRunButtonWrapper-CPvudAWU.js (removed) 1.79 kB 🟢 -1.79 kB 🟢 -646 B 🟢 -597 B
assets/CloudRunButtonWrapper-D8cRPBDu.js (new) 1.79 kB 🔴 +1.79 kB 🔴 +645 B 🔴 +572 B
assets/cloudBadges-CLORO5-B.js (removed) 1.08 kB 🟢 -1.08 kB 🟢 -537 B 🟢 -498 B
assets/cloudBadges-DSpJF3vA.js (new) 1.08 kB 🔴 +1.08 kB 🔴 +539 B 🔴 +482 B
assets/graphHasMissingNodes-BaIjF0fM.js (removed) 1.06 kB 🟢 -1.06 kB 🟢 -461 B 🟢 -419 B
assets/graphHasMissingNodes-CcWjObqM.js (new) 1.06 kB 🔴 +1.06 kB 🔴 +462 B 🔴 +416 B
assets/cloudSubscription-BoZOhUEu.js (new) 976 B 🔴 +976 B 🔴 +466 B 🔴 +400 B
assets/cloudSubscription-CdjFYj--.js (removed) 976 B 🟢 -976 B 🟢 -464 B 🟢 -404 B
assets/nightlyBadges-DcWolZCv.js (new) 595 B 🔴 +595 B 🔴 +357 B 🔴 +310 B
assets/nightlyBadges-wXTaIV9p.js (removed) 595 B 🟢 -595 B 🟢 -356 B 🟢 -309 B
assets/SubscriptionPanelContentWorkspace-BRT84R8a.js (new) 266 B 🔴 +266 B 🔴 +136 B 🔴 +125 B
assets/SubscriptionPanelContentWorkspace-CFHGZeNa.js (removed) 266 B 🟢 -266 B 🟢 -136 B 🟢 -125 B
assets/i18n-BQwgqWbg.js (new) 188 B 🔴 +188 B 🔴 +150 B 🔴 +129 B
assets/i18n-Do-i5KgH.js (removed) 188 B 🟢 -188 B 🟢 -150 B 🟢 -126 B
assets/WidgetInputNumber-DJ8vaXTo.js (new) 186 B 🔴 +186 B 🔴 +119 B 🔴 +121 B
assets/WidgetInputNumber-DxdSVo7u.js (removed) 186 B 🟢 -186 B 🟢 -119 B 🟢 -110 B
assets/WidgetLegacy-CuIsiHKO.js (new) 164 B 🔴 +164 B 🔴 +125 B 🔴 +108 B
assets/WidgetLegacy-DyaDuMR_.js (removed) 164 B 🟢 -164 B 🟢 -125 B 🟢 -113 B
assets/Load3D-BapQyMOM.js (new) 131 B 🔴 +131 B 🔴 +107 B 🔴 +118 B
assets/Load3D-CgfyUqU3.js (removed) 131 B 🟢 -131 B 🟢 -107 B 🟢 -113 B
assets/auto-DWs2ctGL.js 1.73 kB 1.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BEw5ErI4.js 18.5 kB 18.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BGeHkplA.js 17.9 kB 17.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BV0l36Iz.js 17.2 kB 17.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C_Y3D6Cn.js 17.8 kB 17.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C6piRza5.js 19.3 kB 19.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Cf8Zq1td.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CiziP3Xs.js 18 kB 18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D1595tOr.js 19.3 kB 19.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DXauvccL.js 20.6 kB 20.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-P5QCEfZc.js 18 kB 18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-WbYP_D61.js 17 kB 17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LazyImage-ooHoQZNd.js 14.1 kB 14.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BHtk4Fg_.js 174 kB 174 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BMSlgLcp.js 155 kB 155 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BQCWi9e4.js 112 kB 112 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CJicmTR7.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CNOkBy-u.js 126 kB 126 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CySb1R5_.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D0g10ZKf.js 131 kB 131 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DMUPIFMF.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DpsGU4si.js 126 kB 126 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Dz6IPJXM.js 144 kB 144 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-lrEzMywH.js 128 kB 128 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-DUmUhXD6.js 2.38 kB 2.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-CD66_Mw_.js 2 kB 2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-D5feRGXX.js 2.34 kB 2.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-De3MzVmp.js 2.82 kB 2.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/mixpanel.module-DLR992B1.js 143 B 143 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-A7pvB7zM.js 370 kB 370 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BIVjUijC.js 345 kB 345 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Chkn0HaI.js 343 kB 343 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CK_6GHao.js 452 kB 452 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CToVAwnT.js 373 kB 373 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DDabdWgx.js 417 kB 417 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DgvJyE3d.js 386 kB 386 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DjxaeFt_.js 416 kB 416 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DslnWEGg.js 377 kB 377 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-EPAM3kwk.js 373 kB 373 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-NrulhNyH.js 366 kB 366 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-CmezY7As.js 838 B 838 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-cVp-94Rc.js 1.96 kB 1.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-C87scEAV.js 4.21 kB 4.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-BJiJuR5i.js 518 B 518 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-CUtab2CB.js 4.71 kB 4.71 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-D79nBMxa.js 186 B 186 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-CiXfBVBH.js 2.79 kB 2.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-BEfQQjV6.js 3.71 kB 3.71 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-jm_gz6R2.js 4.57 kB 4.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-DnZ84Utk.js 3.79 kB 3.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-WCHoBOIV.js 2.58 kB 2.58 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-BHrU_4qY.js 2.7 kB 2.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-3U-WuCE_.js 3.49 kB 3.49 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-ERx8czR8.js 1.31 kB 1.31 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-BXRA46js.js 3.87 kB 3.87 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-DYS14Ar3.js 3.26 kB 3.26 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-KPj-zM0O.js 573 B 573 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 36 added / 36 removed

@github-actions
Copy link

🔧 Auto-fixes Applied

This PR has been automatically updated to fix linting and formatting issues.

⚠️ Important: Your local branch is now behind. Run git pull before making additional changes to avoid conflicts.

Changes made:

  • ESLint auto-fixes
  • Oxfmt formatting

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

Caution

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

⚠️ Outside diff range comments (1)
src/extensions/core/load3d/Load3dUtils.ts (1)

142-162: Consider migrating uploadThumbnail to use the shared uploadMedia service.

This method still uses direct api.fetchApi('/upload/image') with manual FormData construction, which is the pattern the PR aims to consolidate. For consistency, consider refactoring to use uploadMedia:

 static async uploadThumbnail(
   imageData: string,
   subfolder: string,
   filename: string,
   type: string = 'input'
 ): Promise<boolean> {
-  const blob = await fetch(imageData).then((r) => r.blob())
-  const file = new File([blob], filename, { type: 'image/png' })
-
-  const body = new FormData()
-  body.append('image', file)
-  body.append('subfolder', subfolder)
-  body.append('type', type)
-
-  const resp = await api.fetchApi('/upload/image', {
-    method: 'POST',
-    body
-  })
-
-  return resp.status === 200
+  const result = await uploadMedia(
+    { source: imageData, filename },
+    { subfolder, type: type as ResultItemType }
+  )
+  return result.success
 }

If there's a specific reason to keep the direct implementation (e.g., the type parameter difference), please document it.

🤖 Fix all issues with AI agents
In
`@src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue`:
- Around line 251-274: In uploadFiles, avoid calling assetsStore.updateInputs()
inside the per-file upload loop (uploadPromises) because it triggers redundant
updates; instead call assetsStore.updateInputs() once after awaiting
Promise.all(results) when folder === 'input'. Locate the uploadFiles function
and move the updateInputs() call out of the files.map async callback to run
conditionally after the Promise.all completes, or refactor to use
uploadMediaBatch (mapping files to { source: file } and passing { type: folder
}) and then call assetsStore.updateInputs() once if folder === 'input'.
♻️ Duplicate comments (3)
src/extensions/core/load3d/Load3dUtils.ts (1)

44-47: Localize the temp-upload error message.

The error string on line 45 is user-facing and should be localized. Use the i18n t() function with a key like t('toastMessages.tempUploadFailed', { error: result.error || '' }) and add the corresponding key to src/locales/en/main.json. This aligns with the pattern used in uploadFile() (lines 64-67, 77) where localized messages are used.

🐛 Proposed fix
     if (!result.success || !result.response) {
-      const err = `Error uploading temp file: ${result.error}`
+      const err = t('toastMessages.tempUploadFailed', { error: result.error || '' })
       useToastStore().addAlert(err)
       throw new Error(err)
     }

And add to src/locales/en/main.json:

"tempUploadFailed": "Error uploading temp file: {error}"
src/platform/assets/services/uploadService.ts (1)

52-55: Avoid echoing data URL contents in error messages.

Including a substring of the data URL can leak user content into logs/toasts. Keep this generic.

🔒 Suggested fix
   // dataURL string
   if (!isDataURL(source)) {
-    throw new Error(`Invalid data URL: ${source.substring(0, 50)}...`)
+    throw new Error('Invalid data URL')
   }
src/platform/assets/services/uploadService.test.ts (1)

19-37: Replace as any cast with safer type assertion.

The as any on line 29 violates type safety guidelines. Use as Partial<Response> as Response to explicitly acknowledge the incomplete mock while maintaining type safety.

♻️ Suggested fix
-      vi.mocked(api.fetchApi).mockResolvedValue(mockResponse as any)
+      vi.mocked(api.fetchApi).mockResolvedValue(
+        mockResponse as Partial<Response> as Response
+      )

Apply this pattern to all 7 occurrences in the file (lines 29, 49, 71, 97, 134, 160, 197, 198).

@coderabbitai coderabbitai bot requested a review from DrJKL January 28, 2026 04:32
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: 1

🤖 Fix all issues with AI agents
In
`@src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue`:
- Around line 261-266: The fallback string 'Upload failed' in
WidgetSelectDropdown.vue should be localized: add a key (e.g. "upload.failed")
to src/locales/en/main.json and replace the hardcoded fallback used in the
failedUploads handling (the results/filter block that calls toastStore.addAlert)
with a vue-i18n lookup (via this.$t or useI18n().t) so toastStore.addAlert
receives the translated message (e.g., toastStore.addAlert(t('upload.failed'))
when result.error is absent); ensure the new key is added to other locale files
as needed.

Comment on lines 261 to 266
// Collect failed uploads for error reporting
const failedUploads = results.filter((result) => !result.success)
if (failedUploads.length > 0) {
failedUploads.forEach((result) => {
toastStore.addAlert(result.error || 'Upload failed')
})
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 | 🟠 Major

Localize the upload failure toast fallback.

The fallback string is user-facing and should use vue-i18n. Consider adding a translation key in src/locales/en/main.json.

✅ Suggested change
-      toastStore.addAlert(result.error || 'Upload failed')
+      toastStore.addAlert(
+        result.error || t('widgets.uploadSelect.uploadFailed')
+      )
As per coding guidelines, use vue-i18n for ALL user-facing strings in `src/**/*.{ts,tsx,vue}`.
🤖 Prompt for AI Agents
In `@src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue`
around lines 261 - 266, The fallback string 'Upload failed' in
WidgetSelectDropdown.vue should be localized: add a key (e.g. "upload.failed")
to src/locales/en/main.json and replace the hardcoded fallback used in the
failedUploads handling (the results/filter block that calls toastStore.addAlert)
with a vue-i18n lookup (via this.$t or useI18n().t) so toastStore.addAlert
receives the translated message (e.g., toastStore.addAlert(t('upload.failed'))
when result.error is absent); ensure the new key is added to other locale files
as needed.

christian-byrne and others added 4 commits January 29, 2026 22:56
Creates core uploadService to eliminate ~60-70 LOC duplication across multiple implementations.

Changes:
- Add src/platform/assets/services/uploadService.ts with uploadMedia() and uploadMediaBatch()
- Refactor Load3dUtils to use uploadService (eliminates 50+ LOC)
- Refactor WidgetSelectDropdown to use uploadService (eliminates 20+ LOC)
- Add comprehensive unit tests for uploadService
- Maintain backward compatibility for all existing APIs

Benefits:
- Single source of truth for upload logic
- Consistent error handling
- Type-safe interfaces
- Easier to test and maintain
- Import ImageRef from maskEditorDataStore instead of duplicating
- Replace 'any' with proper UploadApiResponse type
- Add validation for dataURL strings
- Fix test mocking: use vi.spyOn for global.fetch
- Fix uploadMediaBatch test to use distinct response mocks
- Add test for invalid dataURL rejection
- Fix uploadMultipleFiles to return array of successful paths
- Optimize file size test to avoid timeout
- Add back api import to Load3dUtils (needed by fileExists and uploadThumbnail)
- Add null check for result.response in uploadTempImage
- Ensures type safety after rebase on main
- Remove data URL content from error messages (security)
- Replace 'as any' casts with proper type assertions in tests
- Use uploadMediaBatch in WidgetSelectDropdown for single store update
- Localize error strings with i18n (uploadFailed, tempUploadFailed)

Amp-Thread-ID: https://ampcode.com/threads/T-019c0daa-eb18-72eb-bc87-90f09afc3d3a
Co-authored-by: Amp <[email protected]>
@christian-byrne christian-byrne force-pushed the refactor/consolidate-image-upload branch from 7b7f70d to bc0697b Compare January 30, 2026 06:59
@christian-byrne christian-byrne requested a review from a team as a code owner January 30, 2026 06:59
@AustinMroz AustinMroz self-assigned this Jan 30, 2026
Copy link
Collaborator

@AustinMroz AustinMroz left a comment

Choose a reason for hiding this comment

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

Thought we'd be seeing more red than green here, but consolidating the flow of logic still makes changes easier in the future.

@AustinMroz AustinMroz removed their assignment Jan 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants