Skip to content

fix(assets): show text outputs instead of dropping jobs with a text-shaped preview#13292

Open
DeniDoman wants to merge 2 commits into
Comfy-Org:mainfrom
DeniDoman:multimodal_assets_fix
Open

fix(assets): show text outputs instead of dropping jobs with a text-shaped preview#13292
DeniDoman wants to merge 2 commits into
Comfy-Org:mainfrom
DeniDoman:multimodal_assets_fix

Conversation

@DeniDoman

Copy link
Copy Markdown
Contributor

…t dropped

The jobs list synthesizes a node's `text` array from a `preview_output`
object ({content}), but parseTextOutput only accepted raw strings and
discarded the object — leaving the job with no previewable output, so it
was dropped from the Jobs and Generated panels. Read the content from
both the string (detail/subfeed) and object (list) shapes, and un-skip
the realistic text-preview test.
@DeniDoman DeniDoman requested a review from a team June 29, 2026 22:33
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 29, 2026
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

✅ All contributors have signed the CLA. Thank you! This PR is ready to be merged.
Posted by the CLA Assistant Lite bot.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

🎭 Playwright: ⏳ Running...

🎨 Storybook: 🚧 Building...

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Text outputs from node output maps are now parsed into ResultItemImpl entries with synthesized .txt filenames. The zRawJobListItem schema gains an optional outputs field, TaskItemImpl prefers job.outputs over synthesized preview_output data, and the previewOutput getter prioritizes non-text items for thumbnail selection.

Text Output Parsing and Preview Priority

Layer / File(s) Summary
Job schema: outputs field on RawJobListItem
src/platform/remote/comfyui/jobs/jobTypes.ts
Optional outputs moved to zRawJobListItem so zJobDetail inherits it rather than redefining it.
Text parsing helpers and parseNodeOutput routing
src/stores/resultItemParsing.ts, src/stores/resultItemParsing.test.ts
text removed from METADATA_KEYS; getTextContent and parseTextOutput helpers added; parseNodeOutput routes mediaType === 'text' to parseTextOutput with tests covering array and object-shaped text entries.
TaskItemImpl outputs preference and preview filtering
src/stores/queueStore.ts, src/stores/queueStore.test.ts
effectiveOutputs now prefers job.outputs before synthesizing from preview_output; previewOutput excludes text items from the thumbnail candidate pool with tests for text-only and mixed-output jobs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested reviewers

  • dante01yoon

🐇 A rabbit found text in the queue one day,
Mixed with images along the way.
"Parse the words!" the bunny declared,
"But keep the picture front and squared!"
Now .txt files hop out with glee,
While thumbnails stay where thumbnails should be. 🖼️✨


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description only links an issue and omits the required Summary, Changes, Review Focus, and optional sections. Fill in the template with a summary, key changes, review focus, and screenshots or breaking/dependency notes if applicable.
End-To-End Regression Coverage For Fixes ❓ Inconclusive I can verify src changes, no browser_tests, and no E2E rationale in the description, but the PR title/commit subjects aren’t provided verbatim. Please provide the exact PR title and commit subject lines so I can confirm whether a bug-fix signal is present.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the fix for text outputs that were causing jobs to be dropped.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Adr Compliance For Entity/Litegraph Changes ✅ Passed No changed files touch src/lib/litegraph/, src/ecs/, or graph-entity code, so ADR 0003/0008 checks are not applicable.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@DeniDoman

Copy link
Copy Markdown
Contributor Author

I have read and agree to the Contributor License Agreement

comfy-legal added a commit to Comfy-Org/comfy-cla that referenced this pull request Jun 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@src/stores/resultItemParsing.test.ts`:
- Around line 116-147: Add a regression test in resultItemParsing.test.ts for
the direct single-string text shape in parseNodeOutput; the current cases cover
arrays and synthesized preview_output objects, but not text: string. Extend the
existing parseNodeOutput expectations with a case like the other it blocks,
asserting a plain string becomes a single text result item with the correct
content, nodeId, and generated filename so the supported
NodeExecutionOutput['text'] shape stays covered.

In `@src/stores/resultItemParsing.ts`:
- Around line 76-83: Raw string text outputs are being filtered out in
resultItemParsing because the Object.entries(...).filter(...) step only accepts
array values, so parseTextOutput never sees NodeExecutionOutput['text'] when it
is a plain string. Update the logic in resultItemParsing (the flatMap branch
around parseTextOutput and ResultItemImpl creation) to explicitly handle the
'text' mediaType before the array check, accepting both string and array forms
for text while keeping the existing array-only handling for other media types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2402a178-6df2-4aad-8a45-f1cdaf137e66

📥 Commits

Reviewing files that changed from the base of the PR and between 9dcab4e and ef824e7.

📒 Files selected for processing (5)
  • src/platform/remote/comfyui/jobs/jobTypes.ts
  • src/stores/queueStore.test.ts
  • src/stores/queueStore.ts
  • src/stores/resultItemParsing.test.ts
  • src/stores/resultItemParsing.ts

Comment on lines +116 to +147
it('parses array-shaped text outputs into text result items', () => {
const output = makeOutput({
images: [{ filename: 'img.png', subfolder: '', type: 'output' }],
text: ['first', 'second']
})

const result = parseNodeOutput('6', output)

expect(result).toHaveLength(3)

const textItems = result.filter((item) => item.isText)
expect(textItems).toHaveLength(2)
expect(textItems.map((item) => item.content)).toEqual(['first', 'second'])
expect(textItems[0].nodeId).toBe('6')
expect(textItems[0].filename).toBe('6-text-0.txt')
})

it('parses object-shaped text entries from a synthesized preview_output', () => {
const output = makeOutput({
text: [
{ nodeId: '6', mediaType: 'text', content: 'from preview_output' }
] as unknown as NodeExecutionOutput['text']
})

const result = parseNodeOutput('6', output)

expect(result).toHaveLength(1)
expect(result[0].isText).toBe(true)
expect(result[0].content).toBe('from preview_output')
expect(result[0].filename).toBe('6-text-0.txt')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the single-string text case here too.

These tests cover arrays and synthesized preview objects, but the upstream
contract still allows text: string. A direct text: 'hello' regression test
would lock that supported shape in and catch the current parser gap.

Suggested test
+  it('parses single-string text outputs', () => {
+    const result = parseNodeOutput('6', makeOutput({ text: 'hello' }))
+
+    expect(result).toHaveLength(1)
+    expect(result[0].isText).toBe(true)
+    expect(result[0].content).toBe('hello')
+    expect(result[0].filename).toBe('6-text-0.txt')
+  })
📝 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
it('parses array-shaped text outputs into text result items', () => {
const output = makeOutput({
images: [{ filename: 'img.png', subfolder: '', type: 'output' }],
text: ['first', 'second']
})
const result = parseNodeOutput('6', output)
expect(result).toHaveLength(3)
const textItems = result.filter((item) => item.isText)
expect(textItems).toHaveLength(2)
expect(textItems.map((item) => item.content)).toEqual(['first', 'second'])
expect(textItems[0].nodeId).toBe('6')
expect(textItems[0].filename).toBe('6-text-0.txt')
})
it('parses object-shaped text entries from a synthesized preview_output', () => {
const output = makeOutput({
text: [
{ nodeId: '6', mediaType: 'text', content: 'from preview_output' }
] as unknown as NodeExecutionOutput['text']
})
const result = parseNodeOutput('6', output)
expect(result).toHaveLength(1)
expect(result[0].isText).toBe(true)
expect(result[0].content).toBe('from preview_output')
expect(result[0].filename).toBe('6-text-0.txt')
})
it('parses array-shaped text outputs into text result items', () => {
const output = makeOutput({
images: [{ filename: 'img.png', subfolder: '', type: 'output' }],
text: ['first', 'second']
})
const result = parseNodeOutput('6', output)
expect(result).toHaveLength(3)
const textItems = result.filter((item) => item.isText)
expect(textItems).toHaveLength(2)
expect(textItems.map((item) => item.content)).toEqual(['first', 'second'])
expect(textItems[0].nodeId).toBe('6')
expect(textItems[0].filename).toBe('6-text-0.txt')
})
it('parses single-string text outputs', () => {
const result = parseNodeOutput('6', makeOutput({ text: 'hello' }))
expect(result).toHaveLength(1)
expect(result[0].isText).toBe(true)
expect(result[0].content).toBe('hello')
expect(result[0].filename).toBe('6-text-0.txt')
})
it('parses object-shaped text entries from a synthesized preview_output', () => {
const output = makeOutput({
text: [
{ nodeId: '6', mediaType: 'text', content: 'from preview_output' }
] as unknown as NodeExecutionOutput['text']
})
const result = parseNodeOutput('6', output)
expect(result).toHaveLength(1)
expect(result[0].isText).toBe(true)
expect(result[0].content).toBe('from preview_output')
expect(result[0].filename).toBe('6-text-0.txt')
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/resultItemParsing.test.ts` around lines 116 - 147, Add a
regression test in resultItemParsing.test.ts for the direct single-string text
shape in parseNodeOutput; the current cases cover arrays and synthesized
preview_output objects, but not text: string. Extend the existing
parseNodeOutput expectations with a case like the other it blocks, asserting a
plain string becomes a single text result item with the correct content, nodeId,
and generated filename so the supported NodeExecutionOutput['text'] shape stays
covered.

Comment on lines 76 to +83
return Object.entries(nodeOutput)
.filter(([key, value]) => !METADATA_KEYS.has(key) && Array.isArray(value))
.flatMap(([mediaType, items]) =>
(items as unknown[])
.filter(isResultItem)
.map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))
mediaType === 'text'
? parseTextOutput(nodeId, items as unknown[])
: (items as unknown[])
.filter(isResultItem)
.map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Raw text: string outputs are still skipped.

NodeExecutionOutput['text'] still allows a plain string in
src/schemas/apiSchema.ts, but Line 77 only keeps array-valued entries. That
means a response like { text: 'hello' } never reaches parseTextOutput, so
single-string text outputs still disappear.

Suggested fix
 export function parseNodeOutput(
   nodeId: string | number,
   nodeOutput: NodeExecutionOutput | null | undefined
 ): ResultItemImpl[] {
   if (!nodeOutput) return []

   return Object.entries(nodeOutput)
-    .filter(([key, value]) => !METADATA_KEYS.has(key) && Array.isArray(value))
-    .flatMap(([mediaType, items]) =>
-      mediaType === 'text'
-        ? parseTextOutput(nodeId, items as unknown[])
-        : (items as unknown[])
+    .filter(([key]) => !METADATA_KEYS.has(key))
+    .flatMap(([mediaType, value]) =>
+      mediaType === 'text'
+        ? parseTextOutput(
+            nodeId,
+            Array.isArray(value) ? value : value === undefined ? [] : [value]
+          )
+        : Array.isArray(value)
+          ? (value as unknown[])
             .filter(isResultItem)
             .map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))
+          : []
     )
 }
📝 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
return Object.entries(nodeOutput)
.filter(([key, value]) => !METADATA_KEYS.has(key) && Array.isArray(value))
.flatMap(([mediaType, items]) =>
(items as unknown[])
.filter(isResultItem)
.map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))
mediaType === 'text'
? parseTextOutput(nodeId, items as unknown[])
: (items as unknown[])
.filter(isResultItem)
.map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))
return Object.entries(nodeOutput)
.filter(([key]) => !METADATA_KEYS.has(key))
.flatMap(([mediaType, value]) =>
mediaType === 'text'
? parseTextOutput(
nodeId,
Array.isArray(value) ? value : value === undefined ? [] : [value]
)
: Array.isArray(value)
? (value as unknown[])
.filter(isResultItem)
.map((item) => new ResultItemImpl({ ...item, mediaType, nodeId }))
: []
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/resultItemParsing.ts` around lines 76 - 83, Raw string text
outputs are being filtered out in resultItemParsing because the
Object.entries(...).filter(...) step only accepts array values, so
parseTextOutput never sees NodeExecutionOutput['text'] when it is a plain
string. Update the logic in resultItemParsing (the flatMap branch around
parseTextOutput and ResultItemImpl creation) to explicitly handle the 'text'
mediaType before the array check, accepting both string and array forms for text
while keeping the existing array-only handling for other media types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant