Skip to content

feat(persistence): add draft store and tab state management#8519

Merged
christian-byrne merged 5 commits intomainfrom
01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_
Feb 20, 2026
Merged

feat(persistence): add draft store and tab state management#8519
christian-byrne merged 5 commits intomainfrom
01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_

Conversation

@christian-byrne
Copy link
Contributor

@christian-byrne christian-byrne commented Feb 1, 2026

Summary

Adds the Pinia store for managing workflow drafts and a composable for tracking open workflow tabs per browser tab. Uses sessionStorage for tab-specific state to support multiple ComfyUI tabs without conflicts.

Changes

  • What:
    • workflowDraftStoreV2.ts - Pinia store wrapping the LRU cache with save/load/remove operations
    • useWorkflowTabState.ts - Composable for tracking active workflow path and open tabs in sessionStorage (scoped by clientId)
  • Why: Browser tabs need independent workflow state, but the current system uses shared localStorage keys causing tab conflicts

Review Focus

  • Store API design in workflowDraftStoreV2.ts
  • Session vs local storage split in useWorkflowTabState.ts

Part 3 of 4 in the workflow persistence improvements stack

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a per-tab session-scoped composable for workflow pointers, a new Pinia workflow draft store (V2) persisting per-draft payloads in localStorage with LRU eviction/quota handling, storage IO validation helpers, and tests for both features.

Changes

Cohort / File(s) Summary
Tab State
src/platform/workflow/persistence/composables/useWorkflowTabState.ts, src/platform/workflow/persistence/composables/useWorkflowTabState.test.ts
New composable useWorkflowTabState with getActivePath/setActivePath and getOpenPaths/setOpenPaths. Pointers are stored per-browser-tab (scoped by api.clientId/initialClientId) and validated against current workspace ID. Tests cover save/retrieve and workspace-isolation via dynamic imports and mocked clientId.
Draft Store V2
src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts, src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts
Adds useWorkflowDraftStoreV2 (Pinia) storing per-draft payloads in localStorage with a v2 index, payload-first writes, LRU eviction loop on quota errors, payload/index cleanup, and APIs: saveDraft, removeDraft, moveDraft, getDraft, getMostRecentPath, loadPersistedWorkflow, reset. Tests validate saving, eviction, removal, renaming, load fallbacks, and reset.
Storage IO helpers
src/platform/workflow/persistence/base/storageIO.ts
Introduces isQuotaExceeded and isValidIndex helpers; readIndex now validates index shape; writeIndex/writePayload use centralized quota detection and improved validation/error handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Store as Draft Store
    participant LS as LocalStorage
    participant Graph as comfyApp Graph

    Client->>Store: loadPersistedWorkflow(options)
    alt preferredPath present & payload exists
        Store->>LS: read payload by preferredPath
        LS-->>Store: payload
    else preferred missing, fallbackToLatestDraft
        Store->>Store: getMostRecentPath()
        Store->>LS: read payload by mostRecentPath
        LS-->>Store: payload
    else legacy sessionStorage/localStorage fallback
        Store->>LS: read legacy sessionStorage payload
        LS-->>Store: payload or null
        alt none in sessionStorage
            Store->>LS: read legacy localStorage payload
            LS-->>Store: payload or null
        end
    end

    Store->>Graph: tryLoadGraph(payload)
    alt load success
        Graph-->>Store: success
        Store-->>Client: true
    else failure
        Store->>LS: cleanup/remove draft
        Store-->>Client: false
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Poem

🐇 I tuck pointers in session nooks with care,
I stash draft payloads where moonbeams pair,
When quota bells toll, the oldest crumbs hop away,
New paths find sunlight, safe inside their stay,
A little rabbit guards your workflows every day.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a draft store and tab state management for workflow persistence.
Description check ✅ Passed The description covers the required sections (Summary, Changes, Review Focus) with sufficient detail about the implementation and rationale, though slightly less structured than the template.
Docstring Coverage ✅ Passed Docstring coverage is 87.88% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_

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

Copy link
Contributor Author

christian-byrne commented Feb 1, 2026

@github-actions
Copy link

github-actions bot commented Feb 1, 2026

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 02/20/2026, 07:46:06 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

@github-actions
Copy link

github-actions bot commented Feb 1, 2026

Playwright: ✅ 516 passed, 0 failed · 5 flaky

📊 Browser Reports
  • chromium: View Report (✅ 504 / ❌ 0 / ⚠️ 5 / ⏭️ 10)
  • 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)

@christian-byrne christian-byrne changed the title feat(persistence): add V2 store and tab state composable feat(persistence): add draft store and tab state management Feb 2, 2026
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_add_v2_cache_and_storage_i_o_2_4_ branch from 436fa0b to c2e62c7 Compare February 3, 2026 04:46
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch from 0c88af0 to 07ae110 Compare February 3, 2026 04:46
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_add_v2_cache_and_storage_i_o_2_4_ branch from c2e62c7 to c5de23a Compare February 3, 2026 20:38
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch from 07ae110 to f1bc97f Compare February 3, 2026 20:38
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_add_v2_cache_and_storage_i_o_2_4_ branch from c5de23a to 7c42cd4 Compare February 9, 2026 02:33
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch from f1bc97f to 84ac4bd Compare February 9, 2026 02:33
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_add_v2_cache_and_storage_i_o_2_4_ branch from 7c42cd4 to 85a6545 Compare February 18, 2026 23:25
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch 2 times, most recently from a00870f to dd6de90 Compare February 20, 2026 04:37
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_add_v2_cache_and_storage_i_o_2_4_ branch 2 times, most recently from b107ee5 to 4d6cd54 Compare February 20, 2026 05:56
@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch from dd6de90 to b77c98a Compare February 20, 2026 05:56
Base automatically changed from 01-31-feat_persistence_add_v2_cache_and_storage_i_o_2_4_ to main February 20, 2026 06:04
@christian-byrne christian-byrne marked this pull request as ready for review February 20, 2026 06:08
@christian-byrne
Copy link
Contributor Author

@coderabbitai review

@christian-byrne christian-byrne requested a review from a team as a code owner February 20, 2026 06:08
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 20, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Add the V2 draft store and tab state management:

- workflowDraftStoreV2: Uses per-draft localStorage keys instead of a single
  blob. Handles LRU eviction with loop on quota exceeded. Maintains in-memory
  index cache synced with localStorage.

- useWorkflowTabState: Manages sessionStorage pointers scoped by api.clientId.
  Validates workspaceId on read to prevent cross-workspace contamination.

The V2 store is not yet wired to the app - the old store remains active.
This allows the integration to happen in a separate commit.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c16f4-05a2-779d-aa0e-a0e098308a95
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: 2

🧹 Nitpick comments (1)
src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts (1)

80-100: Consider importing MAX_DRAFTS constant instead of magic number.

The test uses hardcoded 32 which duplicates the MAX_DRAFTS constant from draftTypes.ts. If the constant changes, this test could silently test incorrect behavior.

♻️ Suggested improvement
+import { MAX_DRAFTS } from '../base/draftTypes'
+
 // ...

 it('evicts oldest when over limit', () => {
   const store = useWorkflowDraftStoreV2()

-  // Save 32 drafts (MAX_DRAFTS)
-  for (let i = 0; i < 32; i++) {
+  // Save MAX_DRAFTS drafts
+  for (let i = 0; i < MAX_DRAFTS; i++) {
     store.saveDraft(`workflows/draft${i}.json`, `{"id":${i}}`, {
       name: `draft${i}`,
       isTemporary: true
     })
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts` around
lines 80 - 100, Replace the hardcoded 32 in the test with the shared MAX_DRAFTS
constant from draftTypes.ts: import MAX_DRAFTS at the top of the test file and
use it in the save loop (the test around useWorkflowDraftStoreV2() that
currently iterates for (let i = 0; i < 32; i++)), so the test stays correct when
MAX_DRAFTS changes and clearly documents the limit being tested.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/workflow/persistence/base/storageIO.ts`:
- Around line 54-63: The writeIndex (and similarly writePayload) write paths
only treat DOMException with name 'QuotaExceededError' as a quota condition;
update both functions around the localStorage.setItem calls (referencing
StorageKeys.draftIndex and the writePayload storage key usages) to detect quota
errors across browsers by checking: error is a DOMException and (error.name ===
'QuotaExceededError' || error.name === 'NS_ERROR_DOM_QUOTA_REACHED' ||
error.code === 22 || error.code === 1014) or, more generally, using typeof
error.code === 'number' and matching 22/1014; if matched return false (graceful
degradation) and otherwise rethrow the error.
- Around line 31-43: Add strict runtime validation for all four readers:
readIndex, readPayload, readActivePath, and readOpenPaths by defining Zod
schemas (or equivalent type guards) for DraftIndexV2, DraftPayloadV2, active
path, and open paths and using schema.safeParse(parsed) before returning; for
readIndex ensure parsed.v === 2 and validate updatedAt is a number, order is
string[], and entries is an object with DraftEntryMeta values; for readPayload
validate the full DraftPayloadV2 shape; for readActivePath validate it's either
null or a string and for readOpenPaths validate it's an array of strings; on
validation failure return null (and optionally log or swallow error) so no
malformed storage data is cast and propagated.

---

Nitpick comments:
In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts`:
- Around line 80-100: Replace the hardcoded 32 in the test with the shared
MAX_DRAFTS constant from draftTypes.ts: import MAX_DRAFTS at the top of the test
file and use it in the save loop (the test around useWorkflowDraftStoreV2() that
currently iterates for (let i = 0; i < 32; i++)), so the test stays correct when
MAX_DRAFTS changes and clearly documents the limit being tested.

Comment on lines +31 to +43
export function readIndex(workspaceId: string): DraftIndexV2 | null {
if (!storageAvailable) return null

try {
const key = StorageKeys.draftIndex(workspaceId)
const json = localStorage.getItem(key)
if (!json) return null

const parsed = JSON.parse(json)
if (parsed.v !== 2) return null

return parsed as DraftIndexV2
} catch {
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/platform/workflow/persistence/base/storageIO.ts | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3156


🏁 Script executed:

cat -n src/platform/workflow/persistence/base/storageIO.ts | sed -n '100,230p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3905


🏁 Script executed:

# Check the type definitions to understand the expected shapes
cat -n src/platform/workflow/persistence/base/draftTypes.ts | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2740


🏁 Script executed:

# Search for all call sites of these read functions
rg -n 'readIndex\s*\(' src/ --type ts --type vue -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

# Search for all call sites of these read functions
rg -n 'readIndex\s*\(' src/ --type ts -A 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1661


🏁 Script executed:

# Check how readIndex is used in the main consumer
cat -n src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts | sed -n '60,90p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1126


🏁 Script executed:

# Check the removeOrphanedEntries function to see if it validates the stored data
rg -n 'removeOrphanedEntries' src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts -B 5 -A 10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1001


🏁 Script executed:

# Check the draftCacheV2 file for the removeOrphanedEntries implementation
cat -n src/platform/workflow/persistence/base/draftCacheV2.ts | head -150

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 4448


🏁 Script executed:

cat -n src/platform/workflow/persistence/base/draftCacheV2.ts | sed -n '150,200p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1481


🏁 Script executed:

# Search for readPayload call sites
rg -n 'readPayload\s*\(' src/ --type ts -A 2 -B 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3805


🏁 Script executed:

# Check how readPayload is used in the store
cat -n src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts | sed -n '225,260p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1166


🏁 Script executed:

# Search for readActivePath and readOpenPaths call sites
rg -n 'readActivePath\s*\(|readOpenPaths\s*\(' src/ --type ts -A 2 -B 2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3667


🏁 Script executed:

cat -n src/platform/workflow/persistence/composables/useWorkflowTabState.ts | sed -n '35,85p'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1825


🏁 Script executed:

# Check if Zod is used in the project
fd -t f 'package.json' -x cat {} | grep -i zod

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 168


🏁 Script executed:

# Check for existing Zod schema patterns in the codebase
rg -n 'z\.object\|ZodSchema\|z\.parse' src/ --type ts -B 2 -A 5 | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

# Search for Zod imports
rg -n "from 'zod'|from \"zod\"" src/ --type ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1255


Validate parsed storage data before returning.

localStorage/sessionStorage values are user-modifiable; returning parsed JSON without shape validation risks propagating corrupt data. Add type guards or Zod schemas for all four functions:

  • readIndex: Only checks v === 2, lacks validation for updatedAt (number), order (string[]), and entries (object with DraftEntryMeta values)
  • readPayload: No validation; directly casts parsed JSON as DraftPayloadV2
  • readActivePath and readOpenPaths: No validation in readers (though call sites in useWorkflowTabState.ts validate workspaceId, the data shape itself is unchecked)

Zod and zod-validation-error are available in the project. Use type guards or Zod schemas to ensure shape correctness before returning.

Applies to lines 31–46 (readIndex), 69–84 (readPayload), 175–185 (readActivePath), and 205–215 (readOpenPaths).

Per coding guidelines: "Validate trusted sources before processing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/base/storageIO.ts` around lines 31 - 43,
Add strict runtime validation for all four readers: readIndex, readPayload,
readActivePath, and readOpenPaths by defining Zod schemas (or equivalent type
guards) for DraftIndexV2, DraftPayloadV2, active path, and open paths and using
schema.safeParse(parsed) before returning; for readIndex ensure parsed.v === 2
and validate updatedAt is a number, order is string[], and entries is an object
with DraftEntryMeta values; for readPayload validate the full DraftPayloadV2
shape; for readActivePath validate it's either null or a string and for
readOpenPaths validate it's an array of strings; on validation failure return
null (and optionally log or swallow error) so no malformed storage data is cast
and propagated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed — added isValidIndex type guard that validates v === 2, updatedAt is a number, order is an array, and entries is a non-null object.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current implementation of all four reader functions to verify validation
cat -n src/platform/workflow/persistence/base/storageIO.ts | sed -n '26,230p'

Length of output: 6193


@christian-byrne, thanks for adding the isValidIndex type guard—readIndex is now properly validated! 👍

However, the original review flagged four functions that need validation. The remaining three still cast parsed JSON without validation:

  1. readPayload (line 98): Should validate that data is a string and updatedAt is a number before returning as DraftPayloadV2
  2. readActivePath (line 197): Should validate that workspaceId and path are strings before returning as ActivePathPointer
  3. readOpenPaths (line 227): Should validate that workspaceId is a string, paths is a string array, and activeIndex is a number before returning as OpenPathsPointer

You can follow the same pattern as isValidIndex—create type guards like isValidPayload, isValidActivePath, and isValidOpenPaths to ensure malformed storage data doesn't propagate through the system.


🧬 Code Graph Analysis Results
  • File: src/platform/workflow/persistence/base/draftTypes.ts, lines 30-39
export interface DraftIndexV2 {
  /** Schema version */
  v: 2
  /** Last update timestamp */
  updatedAt: number
  /** LRU order: oldest → newest (draftKey array) */
  order: string[]
  /** Metadata keyed by draftKey (hash of path) */
  entries: Record<string, DraftEntryMeta>
}
  • File: src/platform/workflow/persistence/base/draftTypes.ts, lines 46-51
export interface DraftPayloadV2 {
  /** Serialized workflow JSON */
  data: string
  /** Last update timestamp */
  updatedAt: number
}
  • File: src/platform/workflow/persistence/base/draftTypes.ts, lines 59-64
export interface ActivePathPointer {
  /** Workspace ID for validation */
  workspaceId: string
  /** Path to the active workflow */
  path: string
}
  • File: src/platform/workflow/persistence/base/draftTypes.ts, lines 72-79
export interface OpenPathsPointer {
  /** Workspace ID for validation */
  workspaceId: string
  /** Ordered list of open workflow paths */
  paths: string[]
  /** Index of the active workflow in paths array */
  activeIndex: number
}
  • File: src/platform/workflow/persistence/base/storageKeys.ts, lines 40-93
export const StorageKeys = {
  /**
   * Draft index key for localStorage.
   * Contains LRU order and metadata for all drafts.
   */
  draftIndex(workspaceId: string = CURRENT_WORKSPACE_ID): string {
    return `Comfy.Workflow.DraftIndex.v2:${workspaceId}`
  },

  /**
   * Individual draft payload key for localStorage.
   * `@param` path - Workflow path (will be hashed to create key)
   */
  draftPayload(
    path: string,
    workspaceId: string = CURRENT_WORKSPACE_ID
  ): string {
    const draftKey = hashPath(path)
    return `Comfy.Workflow.Draft.v2:${workspaceId}:${draftKey}`
  },

  /**
   * Creates a draft key (hash) from a workflow path.
   */
  draftKey(path: string): string {
    return hashPath(path)
  },

  /**
   * Active workflow pointer key for sessionStorage.
   * `@param` clientId - Browser tab identifier from api.clientId
   */
  activePath(clientId: string): string {
    return `Comfy.Workflow.ActivePath:${clientId}`
  },

  /**
   * Open workflows pointer key for sessionStorage.
   * `@param` clientId - Browser tab identifier from api.clientId
   */
  openPaths(clientId: string): string {
    return `Comfy.Workflow.OpenPaths:${clientId}`
  },

  /**
   * Prefix patterns for cleanup operations.
   */
  prefixes: {
    draftIndex: 'Comfy.Workflow.DraftIndex.v2:',
    draftPayload: 'Comfy.Workflow.Draft.v2:',
    activePath: 'Comfy.Workflow.ActivePath:',
    openPaths: 'Comfy.Workflow.OpenPaths:'
  }
} as const

🧠 Learnings used
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7746
File: src/platform/assets/services/assetService.ts:484-491
Timestamp: 2026-01-06T19:20:56.167Z
Learning: In `src/platform/assets/services/assetService.ts`, prefer using `schema.safeParse()` over `schema.parse()` for validating API responses to avoid throwing ZodError with internal schema details; follow the existing pattern: call `safeParse()`, check `result.success`, and use `fromZodError(result.error)` to format error messages for logging while throwing user-friendly errors.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8307
File: src/stores/bootstrapStore.ts:61-78
Timestamp: 2026-01-26T18:39:26.986Z
Learning: In src/stores/**/*.ts: Do not suggest exposing internal state or error refs from stores unless there is demonstrated active usage of those properties elsewhere in the codebase. Keep internal state private by default; only expose what is actively consumed.

@christian-byrne christian-byrne force-pushed the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch from b77c98a to e07f341 Compare February 20, 2026 06:21
@dosubot dosubot bot removed the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 20, 2026
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Feb 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.

🧹 Nitpick comments (5)
src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts (3)

39-48: Consider exporting the interfaces for external consumers.

DraftMeta and LoadPersistedWorkflowOptions are defined internally but may be needed by callers of saveDraft and loadPersistedWorkflow. If these types are intended for internal use only, this is fine. Otherwise, consider exporting them or moving to draftTypes.ts for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts` around
lines 39 - 48, The interfaces DraftMeta and LoadPersistedWorkflowOptions are
currently unexported but are used by external callers of saveDraft and
loadPersistedWorkflow; export them (or move them into a shared draftTypes.ts and
export from there) so callers can import the types. Update the declarations for
DraftMeta and LoadPersistedWorkflowOptions to be exported and/or add
corresponding exports from draftTypes.ts, then update any imports in callers of
saveDraft/loadPersistedWorkflow to reference the exported types.

134-182: Consider adding a maximum eviction count to prevent runaway loops.

The eviction loop continues while order.length > 0. If storage is persistently full (e.g., other apps consuming localStorage), this could evict all drafts before failing. Consider adding a maximum iteration count to fail faster and preserve more user data.

💡 Suggested improvement
 function handleQuotaExceeded(
   path: string,
   data: string,
   meta: DraftMeta
 ): boolean {
   const index = loadIndex()
   const draftKey = hashPath(path)
+  const maxEvictions = Math.min(5, index.order.length)
+  let evictionCount = 0

   // Try evicting oldest entries until we can write
   let currentIndex = index
-  while (currentIndex.order.length > 0) {
+  while (currentIndex.order.length > 0 && evictionCount < maxEvictions) {
     const oldestKey = currentIndex.order[0]
     if (oldestKey === draftKey) break // Don't evict the one we're trying to save
     // ... rest of loop
+    evictionCount++
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts` around
lines 134 - 182, The eviction loop in handleQuotaExceeded can run until all
drafts are evicted; add a max eviction limit to avoid runaway deletion:
introduce a constant like MAX_EVICTIONS (e.g., 10), add a counter (evictions =
0) and increment it each time you successfully remove an entry in the while loop
inside handleQuotaExceeded, and break out when evictions >= MAX_EVICTIONS (then
call markStorageUnavailable() and return false) so you stop evicting early;
ensure you still avoid evicting the draftKey and that any partially-updated
index state is persisted via persistIndex(finalIndex) only when a new payload
write succeeds (as currently done).

260-275: Consider logging the error with structured context.

The catch block logs the error but doesn't include which path failed. Adding the path would help debugging persisted workflow issues.

💡 Suggested improvement
   } catch (err) {
-    console.error('Failed to load persisted workflow', err)
+    console.error('Failed to load persisted workflow:', { payload: payload?.slice(0, 100), err })
     onFailure?.()
     return false
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts` around
lines 260 - 275, The catch in tryLoadGraph currently logs the error without
context; update the error logging in function tryLoadGraph (which calls
comfyApp.loadGraphData) to include identifying context such as the workflowName
and a safe/truncated representation of payload (or the persistence path if
available) along with the error, and prefer the project logging utility (e.g.,
processLogger or a structured logger) over console.error so logs are structured
and actionable; ensure onFailure() is still invoked and the function returns
false on error.
src/platform/workflow/persistence/composables/useWorkflowTabState.ts (1)

22-24: Verify API initialization edge case.

The getClientId function returns null if neither clientId nor initialClientId is set. This silently prevents any storage operations. Consider whether this edge case (API not yet initialized) should log a warning in development mode to help debug issues where tab state unexpectedly doesn't persist.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/composables/useWorkflowTabState.ts` around
lines 22 - 24, getClientId currently returns null when both api.clientId and
api.initialClientId are absent, causing silent failures of storage; update
getClientId to detect this "API not initialized" edge case and, in development
mode, emit a clear warning (e.g., using console.warn or the project's logger)
that includes which properties are missing (api.clientId and
api.initialClientId) so developers can see why tab state isn't persisting; leave
the return behavior unchanged but only log in dev (use NODE_ENV or the project's
isDev helper) and reference getClientId, api.clientId, and api.initialClientId
when implementing the check and message.
src/platform/workflow/persistence/base/storageIO.ts (1)

111-118: Consider adding storageAvailable guard for consistency.

deletePayload lacks the if (!storageAvailable) return guard that other functions have. While errors are caught and ignored, adding the guard would provide consistent early-exit behavior and avoid unnecessary try/catch overhead when storage is known to be unavailable.

♻️ Suggested improvement
 export function deletePayload(workspaceId: string, draftKey: string): void {
+  if (!storageAvailable) return
+
   try {
     const key = `${StorageKeys.prefixes.draftPayload}${workspaceId}:${draftKey}`
     localStorage.removeItem(key)
   } catch {
     // Ignore errors during deletion
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/base/storageIO.ts` around lines 111 - 118,
The deletePayload function should start by checking the storageAvailable flag
and return early if storage is unavailable to match other functions' behavior;
update deletePayload (which builds the key using
StorageKeys.prefixes.draftPayload and the workspaceId:draftKey pattern) to
include an if (!storageAvailable) return guard before attempting
localStorage.removeItem, leaving the try/catch only as backup for unexpected
runtime errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/platform/workflow/persistence/base/storageIO.ts`:
- Around line 75-80: The parsed JSON for draft payload is being cast directly to
DraftPayloadV2 without validation; update the read routine that builds the key
using StorageKeys.prefixes.draftPayload (the block that calls
localStorage.getItem and JSON.parse) to verify the parsed object has the
expected shape (e.g., typeof parsed.data === 'string' and typeof
parsed.updatedAt === 'number') before casting and returning, and return null (or
handle error) when validation fails, mirroring the approach used in readIndex.
- Around line 175-185: readActivePath and readOpenPaths currently JSON.parse and
cast directly to ActivePathPointer/OpenPathsPointer without validating the
object shape; update both functions (identify by name readActivePath and
readOpenPaths and the key builders StorageKeys.activePath /
StorageKeys.openPaths) to: parse the JSON, perform explicit runtime checks for
required properties and types expected by ActivePathPointer/OpenPathsPointer
(e.g., presence and types of id/path/timestamps or array entries), return the
typed object only if validation passes, otherwise return null; keep try/catch to
swallow malformed JSON but use the validation result to avoid unsafe casts.
- Around line 39-42: The parsed JSON is only checked for parsed.v === 2 but then
blindly cast to DraftIndexV2; add a shape validation step (either a Zod schema
or an explicit type guard) after parsing to verify parsed.updatedAt is a number,
parsed.order is an array of strings, and parsed.entries is a record with the
expected entry shape before returning; if validation fails, return null (or
handle error) so malformed localStorage data cannot be cast to DraftIndexV2.
- Around line 58-63: The catch block in storageIO.ts only checks for error
instanceof DOMException && error.name === 'QuotaExceededError', so to handle
cross-browser quota errors update that condition (in the catch handling around
the storage read/write function) to also detect legacy codes/names: treat as
quota if (error instanceof DOMException && (error.name === 'QuotaExceededError'
|| error.name === 'NS_ERROR_DOM_QUOTA_REACHED')) || (typeof (error as any).code
=== 'number' && ((error as any).code === 22 || (error as any).code === 1014));
when that condition matches return false, otherwise rethrow the error; this
ensures Chrome/Safari numeric code 22 and Firefox code 1014/name
NS_ERROR_DOM_QUOTA_REACHED are handled.
- Around line 100-105: The catch block in the storageIO write path only checks
for DOMException with name 'QuotaExceededError'; update it to detect
cross-browser quota errors like the fix applied to writeIndex by either calling
the shared isQuotaExceeded helper (if available) or expanding the condition to
also check for DOMException.name === 'NS_ERROR_DOM_QUOTA_REACHED' and legacy
numeric codes (e.g. error.code === 22 or error.code === 1014), returning false
for those cases and rethrowing otherwise; reference the catch block in
storageIO.ts and mirror the exact logic used for writeIndex.

---

Nitpick comments:
In `@src/platform/workflow/persistence/base/storageIO.ts`:
- Around line 111-118: The deletePayload function should start by checking the
storageAvailable flag and return early if storage is unavailable to match other
functions' behavior; update deletePayload (which builds the key using
StorageKeys.prefixes.draftPayload and the workspaceId:draftKey pattern) to
include an if (!storageAvailable) return guard before attempting
localStorage.removeItem, leaving the try/catch only as backup for unexpected
runtime errors.

In `@src/platform/workflow/persistence/composables/useWorkflowTabState.ts`:
- Around line 22-24: getClientId currently returns null when both api.clientId
and api.initialClientId are absent, causing silent failures of storage; update
getClientId to detect this "API not initialized" edge case and, in development
mode, emit a clear warning (e.g., using console.warn or the project's logger)
that includes which properties are missing (api.clientId and
api.initialClientId) so developers can see why tab state isn't persisting; leave
the return behavior unchanged but only log in dev (use NODE_ENV or the project's
isDev helper) and reference getClientId, api.clientId, and api.initialClientId
when implementing the check and message.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts`:
- Around line 39-48: The interfaces DraftMeta and LoadPersistedWorkflowOptions
are currently unexported but are used by external callers of saveDraft and
loadPersistedWorkflow; export them (or move them into a shared draftTypes.ts and
export from there) so callers can import the types. Update the declarations for
DraftMeta and LoadPersistedWorkflowOptions to be exported and/or add
corresponding exports from draftTypes.ts, then update any imports in callers of
saveDraft/loadPersistedWorkflow to reference the exported types.
- Around line 134-182: The eviction loop in handleQuotaExceeded can run until
all drafts are evicted; add a max eviction limit to avoid runaway deletion:
introduce a constant like MAX_EVICTIONS (e.g., 10), add a counter (evictions =
0) and increment it each time you successfully remove an entry in the while loop
inside handleQuotaExceeded, and break out when evictions >= MAX_EVICTIONS (then
call markStorageUnavailable() and return false) so you stop evicting early;
ensure you still avoid evicting the draftKey and that any partially-updated
index state is persisted via persistIndex(finalIndex) only when a new payload
write succeeds (as currently done).
- Around line 260-275: The catch in tryLoadGraph currently logs the error
without context; update the error logging in function tryLoadGraph (which calls
comfyApp.loadGraphData) to include identifying context such as the workflowName
and a safe/truncated representation of payload (or the persistence path if
available) along with the error, and prefer the project logging utility (e.g.,
processLogger or a structured logger) over console.error so logs are structured
and actionable; ensure onFailure() is still invoked and the function returns
false on error.

@github-actions
Copy link

github-actions bot commented Feb 20, 2026

📦 Bundle: 4.27 MB gzip ⚪ 0 B

Details

Summary

  • Raw size: 20 MB baseline 20 MB — ⚪ 0 B
  • Gzip: 4.27 MB baseline 4.27 MB — ⚪ 0 B
  • Brotli: 3.3 MB baseline 3.3 MB — ⚪ 0 B
  • Bundles: 219 current • 219 baseline

Category Glance
Vendor & Third-Party ⚪ 0 B (8.7 MB) · Other ⚪ 0 B (7.38 MB) · Data & Services ⚪ 0 B (2.4 MB) · Graph Workspace ⚪ 0 B (914 kB) · Panels & Settings ⚪ 0 B (430 kB) · Views & Navigation ⚪ 0 B (68.6 kB) · + 5 more

App Entry Points — 21.4 kB (baseline 21.4 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-DXvw4xTk.js 21.4 kB 21.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Graph Workspace — 914 kB (baseline 914 kB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-B9tvzP2g.js 914 kB 914 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Views & Navigation — 68.6 kB (baseline 68.6 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudAuthTimeoutView-zGGzxhgg.js 4.88 kB 4.88 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudForgotPasswordView-DTdOs-sI.js 5.53 kB 5.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudLayoutView-DSs2ngVR.js 6.4 kB 6.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudLoginView-CtGB2E8Z.js 10 kB 10 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSignupView-BYlDu3wA.js 7.38 kB 7.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSorryContactSupportView-D9UxxFTg.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSubscriptionRedirectView-C7KWCVfQ.js 4.68 kB 4.68 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudSurveyView-Cku7TaSe.js 15.5 kB 15.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-Bn5Nxcmc.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserCheckView-DmqEHMK0.js 8.41 kB 8.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserSelectView-D5jMMdxl.js 4.5 kB 4.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Panels & Settings — 430 kB (baseline 430 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/AboutPanel-De9h7MZi.js 8.53 kB 8.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudRemoteConfig-umt4sJQo.js 1.41 kB 1.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/config-C5QaqZix.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ExtensionPanel-D1xXPS5P.js 9.35 kB 9.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/KeybindingPanel-B4yCnT7J.js 12.3 kB 12.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LegacyCreditsPanel-8L4dREUL.js 20.6 kB 20.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/refreshRemoteConfig-C3Awg6J8.js 1.14 kB 1.14 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SecretsPanel-B2vCR31v.js 21.5 kB 21.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ServerConfigPanel-DZ3mqvhf.js 6.41 kB 6.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B0mOvnTo.js 32 kB 32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BcrCrxYO.js 27.4 kB 27.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BdMQ9yxb.js 24.2 kB 24.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BMpJMaIw.js 33.7 kB 33.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-C-rh-es6.js 30.1 kB 30.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CgiUGcth.js 23.6 kB 23.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CzjpXwlp.js 38 kB 38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CzpC_oh3.js 28.4 kB 28.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dp3u6l4N.js 29.5 kB 29.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DZjzU5N0.js 28.4 kB 28.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-rQc1NwAI.js 27.6 kB 27.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionPanel-DDfPWhlW.js 18.5 kB 18.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserPanel-CPNo8wNO.js 6.13 kB 6.13 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-D9Ab2T-V.js 357 B 357 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auth-DftDOSgi.js 3.4 kB 3.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/firebaseAuthStore-t8TEtNlA.js 758 B 758 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/PasswordFields-ChCYPNPm.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SignUpForm-KKTHOUCu.js 3.01 kB 3.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UpdatePasswordContent-BRRZFDOi.js 2.34 kB 2.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-DwMbocAN.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Editors & Dialogs — 706 B (baseline 706 B) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-CeIGPmf0.js 706 B 706 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
UI Components — 42.3 kB (baseline 42.3 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/Button-C2yyTmQI.js 2.98 kB 2.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-OjhGOi8-.js 1.24 kB 1.24 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudFeedbackTopbarButton-DuI_8SnP.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyQueueButton-C9WWbt8H.js 763 B 763 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyQueueButton-tBEIXGkY.js 7.17 kB 7.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-29L1zz9s.js 5.96 kB 5.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscribeButton-j-27qSVU.js 2.35 kB 2.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-VWWyqurE.js 7.45 kB 7.45 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-DsMH6rUM.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useTerminalTabs-BQADLljY.js 9.81 kB 9.81 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-CuSXVTtu.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Data & Services — 2.4 MB (baseline 2.4 MB) • ⚪ 0 B

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/api-B5kMHodM.js 648 kB 648 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/audioService-CglNjmXJ.js 1.73 kB 1.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/bootstrapStore-WNEF_PjT.js 2.08 kB 2.08 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/dialogService-46UXvMW_.js 695 B 695 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/dialogService-D_KPOaDC.js 1.63 MB 1.63 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/keybindingService-DpfDbg3X.js 6.52 kB 6.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/load3dService-By23er_5.js 90.9 kB 90.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/releaseStore-DMufpDN7.js 7.96 kB 7.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/releaseStore-YZvFzyZ-.js 730 B 730 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/serverConfigStore-DWKub1Pu.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settingStore-VOeNiDPP.js 714 B 714 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/systemStatsStore-DEdyjZK0.js 12.2 kB 12.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/userStore-D_ublAWr.js 1.85 kB 1.85 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/workflowDraftStore-CMC3mtdt.js 706 B 706 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
Utilities & Hooks — 57.6 kB (baseline 57.6 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/_plugin-vue_export-helper-CY4XIWDa.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/audioUtils-BJkhL9hl.js 858 B 858 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-CeP50apf.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-BQSmRN2Q.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cu9hIV7U.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-Dbobu-mZ.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/subscriptionCheckoutUtil-BQp7cdmA.js 2.53 kB 2.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useCurrentUser-CvrrxzYx.js 692 B 692 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useErrorHandling-Bk9YcIUm.js 1.5 kB 1.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-BmlOeujf.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useFeatureFlags-CBkzzWA0.js 3.5 kB 3.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3d-B4brlmNg.js 829 B 829 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3d-TGXRR_Jn.js 14.6 kB 14.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3dViewer-B0skBhED.js 808 B 808 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useLoad3dViewer-DMOdNRcC.js 14.1 kB 14.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useSubscriptionCredits-BHqYhDpi.js 2.75 kB 2.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useWorkspaceSwitch-B0xphUBz.js 1.25 kB 1.25 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useWorkspaceUI-BB5p9e9b.js 3 kB 3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Vendor & Third-Party — 8.7 MB (baseline 8.7 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-C4mPrLmU.js 70.3 kB 70.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-BVph5xqx.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-BvMr43CG.js 836 kB 836 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-cR3vmlFu.js 131 kB 131 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-oliHT-H5.js 102 kB 102 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-DJyFg6na.js 1.52 MB 1.52 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-gefG45vj.js 1.73 MB 1.73 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-Dg3xKIw9.js 263 kB 263 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-SQwstEKc.js 182 kB 182 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-DSpQy18i.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-Bi_34iZD.js 625 kB 625 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-BjA-tjXK.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-DcEOrMQz.js 112 kB 112 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-C4cqihSk.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-CP_4YO8u.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-DcCUUPIi.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 7.38 MB (baseline 7.38 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/AnimationControls-DTScUogm.js 4.61 kB 4.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-C8-IE0Wi.js 2.69 kB 2.69 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/AudioPreviewPlayer-B-vzsjf8.js 10.8 kB 10.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-P6cmVFBq.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-CPWbq97h.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CancelSubscriptionDialogContent-DXxTZH7t.js 4.76 kB 4.76 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/changeTracker-cvaUMd8M.js 727 B 727 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/changeTracker-CzbYWAxp.js 9.38 kB 9.38 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudBadges-BOI9i6JY.js 1.34 kB 1.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudRunButtonWrapper-DVQ8QSP1.js 1.65 kB 1.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudSessionCookie-l-GVb2KR.js 3.07 kB 3.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/cloudSubscription-B9l0hV4C.js 1.3 kB 1.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-Bzuu84gN.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-CR3OyyCW.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-6ntdtqc9.js 16.6 kB 16.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B4RbG9Th.js 17.2 kB 17.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BmIbopvA.js 14.9 kB 14.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Br2FiHkj.js 17.1 kB 17.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BrlWvwVz.js 15.7 kB 15.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BV_NDvWz.js 15.5 kB 15.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CSxUmE31.js 14.7 kB 14.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D3gGGUgv.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DBqn--Z9.js 15.8 kB 15.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DgX9pWvG.js 15.8 kB 15.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DlSpFU8-.js 18.4 kB 18.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-BKU8e69-.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/core-e7u6Fd6p.js 72.3 kB 72.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CreateWorkspaceDialogContent-BD4Qq-Uz.js 5.5 kB 5.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CurrentUserPopoverWorkspace-C4WowD-A.js 19.8 kB 19.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/DeleteWorkspaceDialogContent-C9XT51li.js 4.2 kB 4.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/EditWorkspaceDialogContent-9-3fnbq6.js 5.3 kB 5.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/GlobalToast-BUldV_2n.js 2.91 kB 2.91 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/graphHasMissingNodes-BKgQfPdb.js 761 B 761 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/groupNode-_1b28fjz.js 72.1 kB 72.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-CL7APlQN.js 199 B 199 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-DTVukeiF.js 505 kB 505 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/InviteMemberDialogContent-CzxgB955.js 7.35 kB 7.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/InviteMemberUpsellDialogContent-c72Q4r_q.js 3.79 kB 3.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LazyImage-C812ZTaN.js 12.3 kB 12.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/LeaveWorkspaceDialogContent-D8ssFmQu.js 4.03 kB 4.03 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3D-afnwqz-g.js 1.04 kB 1.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/load3d-Com6Zxnu.js 14.7 kB 14.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3D-Do-72Aj4.js 16.2 kB 16.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3DConfiguration-DYUx7sFS.js 6.27 kB 6.27 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3DControls-HGDMLBZT.js 30.9 kB 30.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3dViewerContent-BDwKe3lk.js 23 kB 23 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Load3dViewerContent-D8k3_ibp.js 963 B 963 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bg-1LcRk.js 120 kB 120 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BhTuc8pp.js 144 kB 144 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BjNyGyrP.js 136 kB 136 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bl8Fy3rY.js 141 kB 141 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bo3hm2ug.js 190 kB 190 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C-XiaTzV.js 157 kB 157 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D-LaWT4q.js 121 kB 121 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DkPPQYpL.js 170 kB 170 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-paKyiK5-.js 137 kB 137 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-wXc1MGIE.js 139 kB 139 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-XEp46eGp.js 164 kB 164 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-uVW6UxXn.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-Bo-yoCQs.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-BWaFQR3Q.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-_lQOgDGQ.js 2.23 kB 2.23 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nightlyBadges-myT0g9eD.js 971 B 971 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Bheu43Bb.js 391 kB 391 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BPuceShu.js 346 kB 346 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BvgDpig6.js 377 kB 377 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BZpu0NoU.js 463 kB 463 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cev_L1ap.js 381 kB 381 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CGC_NG3k.js 369 kB 369 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cj5X8IGf.js 343 kB 343 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DAEaB4hJ.js 373 kB 373 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DJG4rq9g.js 377 kB 377 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DJl7ITPK.js 424 kB 424 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Dywtk58C.js 424 kB 424 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeTemplates-C4kNMxJ_.js 9.27 kB 9.27 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Preview3d-gHyAGaHs.js 4.78 kB 4.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-CxcvRxyJ.js 665 B 665 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/RemoveMemberDialogContent-KiS0jH5f.js 4.01 kB 4.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/RevokeInviteDialogContent-Cv1W1rq6.js 3.92 kB 3.92 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/saveMesh-CCl1MXOP.js 3.35 kB 3.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-CVx7rq6H.js 8.94 kB 8.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SignInContent-scRsBIb-.js 18.9 kB 18.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-C5LE3AZS.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-DH4DCwOw.js 3.52 kB 3.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-hEkx01Ix.js 251 B 251 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscribeToRun-Cd7MeGAw.js 2.2 kB 2.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionPanelContentWorkspace-D2XVLMCW.js 21.6 kB 21.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionPanelContentWorkspace-DBSR7D3P.js 900 B 900 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionRequiredDialogContent-CZ0nwrr0.js 26.2 kB 26.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionRequiredDialogContentWorkspace-BZqvFk3y.js 45.8 kB 45.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-zZf2dHJ2.js 226 B 226 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ValueControlPopover-Cht8rFOc.js 4.89 kB 4.89 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-DTUjK0ZE.js 445 B 445 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-B-1BUI45.js 3.19 kB 3.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-DmqJdQyE.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-B9WHn-7I.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-LYMr5zb-.js 2.9 kB 2.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-Daz0soUA.js 3.61 kB 3.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-Ds3saEb3.js 3.1 kB 3.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCrop-de6uvcxY.js 22.1 kB 22.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputNumber-BuygX2ag.js 437 B 437 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputNumber-DesANYn0.js 11.8 kB 11.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-DTX5OxDV.js 1.86 kB 1.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-CDiEG33G.js 1.95 kB 1.95 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLegacy-DcbDZli6.js 715 B 715 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-CYq8CHI1.js 2.88 kB 2.88 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-DfMXJwGW.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetRecordAudio-DltXyE01.js 17.3 kB 17.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetSelect-DKXNsQZo.js 57.8 kB 57.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-BCCY3me8.js 3.18 kB 3.18 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-BjKgNacU.js 6.8 kB 6.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-Cp8f93Pk.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetWithControl-Dy-08-7y.js 7.01 kB 7.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspacePanelContent-CCBTadME.js 29.2 kB 29.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

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

🧹 Nitpick comments (3)
src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts (2)

124-142: Consider adding test for moveDraft write failure scenario.

Given the data loss risk identified in the implementation, a test verifying behavior when writePayload fails during move would improve coverage and serve as a regression guard.

Would you like me to draft a test case that mocks writePayload to fail and verifies the old payload is preserved?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts` around
lines 124 - 142, Add a test for the moveDraft failure path that mocks the
underlying write operation to throw and verifies the original draft is
preserved; specifically, in workflowDraftStoreV2.test.ts create a new test
alongside the existing moveDraft spec that uses useWorkflowDraftStoreV2, calls
saveDraft('workflows/old.json', ...), stubs or mocks writePayload (the internal
persistence method used by moveDraft) to throw an error when writing the new
path, then call store.moveDraft('workflows/old.json','workflows/new.json','new')
and assert the call results in the expected error/exception and that
store.getDraft('workflows/old.json') still returns the original payload while
store.getDraft('workflows/new.json') is null (ensuring no data loss).

80-100: Consider importing MAX_DRAFTS constant for clarity.

The test uses hardcoded 32 which works but could drift if the constant changes. Importing MAX_DRAFTS would make the test self-documenting and resilient.

♻️ Proposed improvement
+import { MAX_DRAFTS } from '../base/draftTypes'
+
 // ...
 
     it('evicts oldest when over limit', () => {
       const store = useWorkflowDraftStoreV2()

-      // Save 32 drafts (MAX_DRAFTS)
-      for (let i = 0; i < 32; i++) {
+      // Save MAX_DRAFTS drafts
+      for (let i = 0; i < MAX_DRAFTS; i++) {
         store.saveDraft(`workflows/draft${i}.json`, `{"id":${i}}`, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts` around
lines 80 - 100, The test hardcodes 32 instead of using the MAX_DRAFTS constant
which can drift; update the test to import MAX_DRAFTS and use it when creating
drafts and iterating (replace the literal 32 in the for loop and comment with
MAX_DRAFTS) so the eviction assertion around useWorkflowDraftStoreV2.saveDraft
and getDraft stays correct if MAX_DRAFTS changes.
src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts (1)

148-152: Simplify entry lookup using direct key access.

entries is keyed by draftKey, so you can access the entry directly instead of iterating through all values.

♻️ Proposed simplification
-      const oldestEntry = Object.values(currentIndex.entries).find(
-        (e) => hashPath(e.path) === oldestKey
-      )
-      if (!oldestEntry) break
+      const oldestEntry = currentIndex.entries[oldestKey]
+      if (!oldestEntry) break
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts` around
lines 148 - 152, The lookup for the oldest entry is inefficiently scanning
Object.values(currentIndex.entries) and comparing hashPath(e.path) ===
oldestKey; since currentIndex.entries is keyed by draftKey (the same key stored
in oldestKey), replace the find with a direct property access
currentIndex.entries[oldestKey] and keep the null check (if not found then
break). Update references around hashPath(e.path) === oldestKey to use the
direct key lookup to simplify and speed up eviction logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts`:
- Around line 200-218: The moveDraft function currently reads the old payload
(readPayload), calls writePayload to write to result.newKey, then
unconditionally calls deletePayload and persistIndex; if writePayload fails this
causes data loss. Change moveDraft (and the local flow around moveEntry,
readPayload, writePayload, deletePayload, persistIndex) to verify the
writePayload result or catch its thrown error before deleting the old payload:
attempt the write inside a try/catch (or check its return value), only call
deletePayload(workspaceId, result.oldKey) and persistIndex(result.index) after a
confirmed successful write, and on failure log the error and abort/return
without mutating the index or deleting the original payload (or propagate the
error) so the draft remains intact. Ensure workspaceId/result keys are preserved
in the error handling logic.

---

Nitpick comments:
In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.test.ts`:
- Around line 124-142: Add a test for the moveDraft failure path that mocks the
underlying write operation to throw and verifies the original draft is
preserved; specifically, in workflowDraftStoreV2.test.ts create a new test
alongside the existing moveDraft spec that uses useWorkflowDraftStoreV2, calls
saveDraft('workflows/old.json', ...), stubs or mocks writePayload (the internal
persistence method used by moveDraft) to throw an error when writing the new
path, then call store.moveDraft('workflows/old.json','workflows/new.json','new')
and assert the call results in the expected error/exception and that
store.getDraft('workflows/old.json') still returns the original payload while
store.getDraft('workflows/new.json') is null (ensuring no data loss).
- Around line 80-100: The test hardcodes 32 instead of using the MAX_DRAFTS
constant which can drift; update the test to import MAX_DRAFTS and use it when
creating drafts and iterating (replace the literal 32 in the for loop and
comment with MAX_DRAFTS) so the eviction assertion around
useWorkflowDraftStoreV2.saveDraft and getDraft stays correct if MAX_DRAFTS
changes.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts`:
- Around line 148-152: The lookup for the oldest entry is inefficiently scanning
Object.values(currentIndex.entries) and comparing hashPath(e.path) ===
oldestKey; since currentIndex.entries is keyed by draftKey (the same key stored
in oldestKey), replace the find with a direct property access
currentIndex.entries[oldestKey] and keep the null check (if not found then
break). Update references around hashPath(e.path) === oldestKey to use the
direct key lookup to simplify and speed up eviction logic.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts`:
- Around line 166-175: The index persistence result is ignored in the
quota-eviction branch: after calling upsertEntry and persistIndex in the block
around upsertEntry/persistIndex, check persistIndex's boolean result (same
pattern used in saveDraft) and if it returns false, remove the newly-written
payload (call the same cleanup function used elsewhere, e.g. deleteDraftPayload
or deletePayload) and return false instead of true; otherwise return true.
Ensure you reference the upsertEntry, persistIndex and the payload-delete helper
used by saveDraft so the behavior mirrors saveDraft's error handling.

---

Duplicate comments:
In `@src/platform/workflow/persistence/base/storageIO.ts`:
- Around line 36-62: isValidIndex only checks top-level types and lets
user-modified storage leak invalid shapes; extend runtime validation by adding
deeper guards (e.g., isValidPayload, isValidActivePath, isValidOpenPaths or Zod
schemas with safeParse) and use them inside readIndex (and any
payload/tab-pointer readers) to validate that order is an array of strings,
entries is a map whose values match the DraftPayload shape, and active/open path
pointers match expected string/array formats; if any of these new validators
fail, return null instead of casting/propagating the parsed JSON.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts`:
- Around line 142-152: The eviction loop in workflowDraftStoreV2.ts can
prematurely break when the draft being saved (draftKey) is the oldest
(currentIndex.order[0]), preventing other entries from being evicted; modify the
loop that uses currentIndex, currentIndex.order, oldestKey and draftKey so that
when oldestKey === draftKey you skip that key instead of breaking (e.g., advance
to the next key or remove that single key from the temporary scan list) and
continue eviction attempts until either space is freed or no other entries
remain; ensure you still avoid evicting the draft being saved and preserve the
original order semantics for remaining entries.
- Around line 317-328: Wrap direct sessionStorage/localStorage reads inside
try-catch in loadPersistedWorkflow in workflowDraftStoreV2 (around the
clientId/sessionPayload and localPayload reads) so thrown DOMExceptions become
soft misses: for the sessionStorage block, guard retrieving
`sessionStorage.getItem(\`workflow:${clientId}\`)` in a try-catch and only call
`tryLoadGraph(sessionPayload, workflowName)` if the read succeeded; for the
localStorage fallback, similarly guard `localStorage.getItem('workflow')` in a
try-catch and return await tryLoadGraph(localPayload, workflowName) only when
read succeeded, otherwise proceed as a miss (return false or continue the
existing flow) so storage access failures do not throw out of
loadPersistedWorkflow.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts`:
- Around line 203-218: moveDraft currently writes the new payload then deletes
the old payload before ensuring persistIndex succeeded, risking orphaned/new
payload loss; change the sequence to write the new payload (writePayload with
result.newKey), then call persistIndex(result.index) and only if persistIndex
succeeds deletePayload(workspaceId, result.oldKey); if persistIndex fails, roll
back by deleting the newly written payload (deletePayload(workspaceId,
result.newKey)) and return/fail gracefully; refer to moveDraft, loadIndex,
moveEntry, readPayload, writePayload, deletePayload, and persistIndex to locate
the change.

Comment on lines +203 to +218
function moveDraft(oldPath: string, newPath: string, name: string): void {
const index = loadIndex()
const result = moveEntry(index, oldPath, newPath, name)

if (result) {
const oldPayload = readPayload(workspaceId, result.oldKey)
if (oldPayload) {
const written = writePayload(workspaceId, result.newKey, {
data: oldPayload.data,
updatedAt: Date.now()
})
if (!written) return
deletePayload(workspaceId, result.oldKey)
}
persistIndex(result.index)
}
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

Prevent draft loss when index persistence fails in moveDraft.

If the index write fails after the new payload is written, the old payload can be deleted while the stored index still points to the old key. On reload, the new payload becomes orphaned and is removed, losing the draft. Persist the index before deleting the old payload and roll back on failure.

🐛 Proposed fix
 function moveDraft(oldPath: string, newPath: string, name: string): void {
   const index = loadIndex()
   const result = moveEntry(index, oldPath, newPath, name)

   if (result) {
+    const previousIndex = indexCache.value ?? index
     const oldPayload = readPayload(workspaceId, result.oldKey)
     if (oldPayload) {
       const written = writePayload(workspaceId, result.newKey, {
         data: oldPayload.data,
         updatedAt: Date.now()
       })
       if (!written) return
-      deletePayload(workspaceId, result.oldKey)
-    }
-    persistIndex(result.index)
+      const persisted = persistIndex(result.index)
+      if (!persisted) {
+        indexCache.value = previousIndex
+        deletePayload(workspaceId, result.newKey)
+        return
+      }
+      deletePayload(workspaceId, result.oldKey)
+      return
+    }
+    if (!persistIndex(result.index)) {
+      indexCache.value = previousIndex
+    }
   }
 }

As per coding guidelines: “Implement proper error handling”.

📝 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
function moveDraft(oldPath: string, newPath: string, name: string): void {
const index = loadIndex()
const result = moveEntry(index, oldPath, newPath, name)
if (result) {
const oldPayload = readPayload(workspaceId, result.oldKey)
if (oldPayload) {
const written = writePayload(workspaceId, result.newKey, {
data: oldPayload.data,
updatedAt: Date.now()
})
if (!written) return
deletePayload(workspaceId, result.oldKey)
}
persistIndex(result.index)
}
function moveDraft(oldPath: string, newPath: string, name: string): void {
const index = loadIndex()
const result = moveEntry(index, oldPath, newPath, name)
if (result) {
const previousIndex = indexCache.value ?? index
const oldPayload = readPayload(workspaceId, result.oldKey)
if (oldPayload) {
const written = writePayload(workspaceId, result.newKey, {
data: oldPayload.data,
updatedAt: Date.now()
})
if (!written) return
const persisted = persistIndex(result.index)
if (!persisted) {
indexCache.value = previousIndex
deletePayload(workspaceId, result.newKey)
return
}
deletePayload(workspaceId, result.oldKey)
return
}
if (!persistIndex(result.index)) {
indexCache.value = previousIndex
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/workflow/persistence/stores/workflowDraftStoreV2.ts` around
lines 203 - 218, moveDraft currently writes the new payload then deletes the old
payload before ensuring persistIndex succeeded, risking orphaned/new payload
loss; change the sequence to write the new payload (writePayload with
result.newKey), then call persistIndex(result.index) and only if persistIndex
succeeds deletePayload(workspaceId, result.oldKey); if persistIndex fails, roll
back by deleting the newly written payload (deletePayload(workspaceId,
result.newKey)) and return/fail gracefully; refer to moveDraft, loadIndex,
moveEntry, readPayload, writePayload, deletePayload, and persistIndex to locate
the change.

@christian-byrne christian-byrne merged commit 1166855 into main Feb 20, 2026
29 checks passed
@christian-byrne christian-byrne deleted the 01-31-feat_persistence_rewrite_store_with_per-draft_keys_3_4_ branch February 20, 2026 07:53
huntcsg pushed a commit that referenced this pull request Feb 21, 2026
## Summary

Adds the Pinia store for managing workflow drafts and a composable for
tracking open workflow tabs per browser tab. Uses sessionStorage for
tab-specific state to support multiple ComfyUI tabs without conflicts.

## Changes

- **What**: 
- `workflowDraftStoreV2.ts` - Pinia store wrapping the LRU cache with
save/load/remove operations
- `useWorkflowTabState.ts` - Composable for tracking active workflow
path and open tabs in sessionStorage (scoped by clientId)
- **Why**: Browser tabs need independent workflow state, but the current
system uses shared localStorage keys causing tab conflicts

## Review Focus

- Store API design in `workflowDraftStoreV2.ts`
- Session vs local storage split in `useWorkflowTabState.ts`

---
*Part 3 of 4 in the workflow persistence improvements stack*

---------

Co-authored-by: Amp <amp@ampcode.com>
christian-byrne added a commit that referenced this pull request Feb 22, 2026
- Category sidebar: nested expanded subfolders wrap in bg-secondary-background container
- Chevrons appear on sidebar hover for categories with children
- Filter chips: three visual states (active/applied/default) with applied count
- Preview card pricing badge truncation for overflow
- Pass appliedFilters prop to NodeSearchFilterBar
- Fix paddingLeft to preserve depth hierarchy when chevrons are visible
- Deduplicate button template in NodeSearchCategoryTreeNode
- Restore draftTypes.ts removed by merge conflict with #8993/#8519

Amp-Thread-ID: https://ampcode.com/threads/T-019c8410-57a4-7432-85f0-44ffb23788e2
christian-byrne added a commit that referenced this pull request Feb 22, 2026
- Category sidebar: nested expanded subfolders wrap in bg-secondary-background container
- Chevrons appear on sidebar hover for categories with children
- Filter chips: three visual states (active/applied/default) with applied count
- Preview card pricing badge truncation for overflow
- Pass appliedFilters prop to NodeSearchFilterBar
- Fix paddingLeft to preserve depth hierarchy when chevrons are visible
- Deduplicate button template in NodeSearchCategoryTreeNode
- Restore draftTypes.ts removed by merge conflict with #8993/#8519

Amp-Thread-ID: https://ampcode.com/threads/T-019c8410-57a4-7432-85f0-44ffb23788e2
christian-byrne added a commit that referenced this pull request Feb 23, 2026
- Category sidebar: nested expanded subfolders wrap in bg-secondary-background container
- Chevrons appear on sidebar hover for categories with children
- Filter chips: three visual states (active/applied/default) with applied count
- Preview card pricing badge truncation for overflow
- Pass appliedFilters prop to NodeSearchFilterBar
- Fix paddingLeft to preserve depth hierarchy when chevrons are visible
- Deduplicate button template in NodeSearchCategoryTreeNode
- Restore draftTypes.ts removed by merge conflict with #8993/#8519

Amp-Thread-ID: https://ampcode.com/threads/T-019c87f4-aa28-7290-bdf0-ea5f86aacde3
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.

1 participant