{filePaths.length > 0 ? (
@@ -165,14 +172,14 @@ function OutputFieldInput({ field, onValueChange, taskUpdatedAt }: { field: Outp
return (
handleChange(e.target.value)}
- placeholder={`Enter ${field.name.toLowerCase()}...`}
+ placeholder={`Enter ${fieldLabel.toLowerCase()}...`}
/>
)
@@ -181,7 +188,7 @@ function OutputFieldInput({ field, onValueChange, taskUpdatedAt }: { field: Outp
return (
handleChange(e.target.value)}
- placeholder={`Enter ${field.name.toLowerCase()}...`}
+ placeholder={`Enter ${fieldLabel.toLowerCase()}...`}
/>
)
diff --git a/src/renderer/src/stores/task-store.test.ts b/src/renderer/src/stores/task-store.test.ts
index dbe3269..d5cee3b 100644
--- a/src/renderer/src/stores/task-store.test.ts
+++ b/src/renderer/src/stores/task-store.test.ts
@@ -175,8 +175,37 @@ describe('useTaskStore', () => {
expect(task.skill_ids).toEqual(['s1', 's2'])
})
+ it('sanitizes malformed output fields from external task updates', async () => {
+ const taskWithMalformedOutputs = {
+ id: 't3',
+ title: 'Agent-updated task',
+ output_fields: [
+ { id: 'pr_url', value: 'https://example.com/pr/123' },
+ { name: 'Approval', type: 'toggle', value: true, options: ['yes', 1, null] }
+ ]
+ }
+ ;(mockElectronAPI.db.getTasks as unknown as Mock).mockResolvedValue([taskWithMalformedOutputs])
+
+ await useTaskStore.getState().fetchTasks()
+
+ const [firstField, secondField] = useTaskStore.getState().tasks[0].output_fields
+ expect(firstField).toMatchObject({
+ id: 'pr_url',
+ name: 'pr url',
+ type: 'text',
+ value: 'https://example.com/pr/123'
+ })
+ expect(secondField).toMatchObject({
+ id: 'output_field_2',
+ name: 'Approval',
+ type: 'text',
+ value: true,
+ options: ['yes']
+ })
+ })
+
it('preserves skill_ids null (agent defaults)', async () => {
- const taskWithNullSkills = { id: 't3', title: 'Task', skill_ids: null }
+ const taskWithNullSkills = { id: 't4', title: 'Task', skill_ids: null }
;(mockElectronAPI.db.getTasks as unknown as Mock).mockResolvedValue([taskWithNullSkills])
await useTaskStore.getState().fetchTasks()
diff --git a/src/renderer/src/stores/task-store.ts b/src/renderer/src/stores/task-store.ts
index a25afc0..8f77025 100644
--- a/src/renderer/src/stores/task-store.ts
+++ b/src/renderer/src/stores/task-store.ts
@@ -1,7 +1,39 @@
import { create } from 'zustand'
-import type { WorkfloTask, CreateTaskDTO, UpdateTaskDTO } from '@/types'
+import type { WorkfloTask, CreateTaskDTO, UpdateTaskDTO, OutputField, OutputFieldType } from '@/types'
import { taskApi, taskSourceApi, onTaskUpdated, onTaskCreated, onTasksRefresh } from '@/lib/ipc-client'
+const VALID_OUTPUT_FIELD_TYPES = new Set
([
+ 'text',
+ 'number',
+ 'email',
+ 'textarea',
+ 'list',
+ 'date',
+ 'file',
+ 'boolean',
+ 'country',
+ 'currency',
+ 'url'
+])
+
+function normalizeOutputField(field: unknown, index: number): OutputField | null {
+ if (!field || typeof field !== 'object') return null
+
+ const raw = field as Partial
+ const id = typeof raw.id === 'string' && raw.id.trim() ? raw.id : `output_field_${index + 1}`
+ const name = typeof raw.name === 'string' && raw.name.trim() ? raw.name : id.replace(/_/g, ' ')
+ const rawType = typeof raw.type === 'string' ? raw.type : ''
+ const type = VALID_OUTPUT_FIELD_TYPES.has(rawType as OutputFieldType) ? (rawType as OutputFieldType) : 'text'
+
+ return {
+ ...raw,
+ id,
+ name,
+ type,
+ options: Array.isArray(raw.options) ? raw.options.filter((option): option is string => typeof option === 'string') : undefined
+ }
+}
+
/** Ensure array fields on a task are always proper arrays (guards against undefined/null from external sources) */
function normalizeTask(task: WorkfloTask): WorkfloTask {
return {
@@ -9,7 +41,11 @@ function normalizeTask(task: WorkfloTask): WorkfloTask {
labels: Array.isArray(task.labels) ? task.labels : [],
repos: Array.isArray(task.repos) ? task.repos : [],
attachments: Array.isArray(task.attachments) ? task.attachments : [],
- output_fields: Array.isArray(task.output_fields) ? task.output_fields : [],
+ output_fields: Array.isArray(task.output_fields)
+ ? task.output_fields
+ .map((field, index) => normalizeOutputField(field, index))
+ .filter((field): field is OutputField => field !== null)
+ : [],
skill_ids: task.skill_ids == null ? null : Array.isArray(task.skill_ids) ? task.skill_ids : []
}
}