From 3549a4ba63c707eb84f875c753f997a63365a742 Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Wed, 15 Apr 2026 15:44:21 +0200 Subject: [PATCH 1/3] fix(steps): guard against undefined summary crashing panel StepEntry.summary is typed as required but AI-written steps.json may omit it; truncate() called text.length unconditionally, throwing TypeError: Cannot read properties of undefined (reading 'length'). Added ?? '' fallback matching existing pattern for detail/status. --- src/components/TaskStepsSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TaskStepsSection.tsx b/src/components/TaskStepsSection.tsx index b1b693ef..aff0b9bb 100644 --- a/src/components/TaskStepsSection.tsx +++ b/src/components/TaskStepsSection.tsx @@ -284,7 +284,7 @@ export function TaskStepsSection(props: TaskStepsSectionProps) { flex: '1', }} > - {truncate(step.summary, 140)} + {truncate(step.summary ?? '', 140)} @@ -355,7 +355,7 @@ export function TaskStepsSection(props: TaskStepsSectionProps) { flex: '1', }} > - {truncate(step().summary, 140)} + {truncate(step().summary ?? '', 140)} Date: Wed, 15 Apr 2026 15:45:38 +0200 Subject: [PATCH 2/3] fix(steps): validate step entries and guard files_touched array check Filter non-object entries in setStepsContent so null/primitive values in steps.json never reach the render. Also tighten the files_touched Show guard to Array.isArray to prevent iterating non-array values. --- src/components/TaskStepsSection.tsx | 6 +++++- src/store/tasks.ts | 10 ++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/TaskStepsSection.tsx b/src/components/TaskStepsSection.tsx index aff0b9bb..18e4de0a 100644 --- a/src/components/TaskStepsSection.tsx +++ b/src/components/TaskStepsSection.tsx @@ -303,7 +303,11 @@ export function TaskStepsSection(props: TaskStepsSectionProps) { {truncate(step.detail ?? '', 280)} - 0}> + 0 + } + >
0 ? (steps as StepEntry[]) : undefined, - ); + const valid = steps + ? (steps.filter((s) => s !== null && typeof s === 'object' && !Array.isArray(s)) as StepEntry[]) + : []; + setStore('tasks', taskId, 'stepsContent', valid.length > 0 ? valid : undefined); } export function setTaskLastInputAt(taskId: string): void { From 6cbde935161a181b0641865acb3b3c73741896d5 Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Wed, 15 Apr 2026 16:18:27 +0200 Subject: [PATCH 3/3] fix(dev): preserve tasks on reload and exclude sibling worktrees from watch - Expand preload allowlist check to match both single- and double-quoted channel names in preload.cjs (verifyPreloadAllowlist) - Exclude sibling git worktree directories from Vite's file watcher to prevent spurious HMR reloads during multi-worktree dev sessions - Persist task state immediately after createTask so tasks survive a dev reload; fire-and-forget since saveState handles errors internally --- electron/main.ts | 4 +++- electron/vite.config.electron.ts | 10 ++++++++++ src/store/tasks.ts | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/electron/main.ts b/electron/main.ts index 17bd0822..21e4ac36 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -51,7 +51,9 @@ function verifyPreloadAllowlist(): void { const preloadPath = path.join(__dirname, '..', 'electron', 'preload.cjs'); const preloadSrc = fs.readFileSync(preloadPath, 'utf8'); const enumValues = new Set(Object.values(IPC)); - const missing = [...enumValues].filter((v) => !preloadSrc.includes(`"${v}"`)); + const hasChannel = (channel: string) => + preloadSrc.includes(`'${channel}'`) || preloadSrc.includes(`"${channel}"`); + const missing = [...enumValues].filter((v) => !hasChannel(v)); if (missing.length > 0) { console.warn( `[preload-sync] IPC channels missing from preload.cjs ALLOWED_CHANNELS: ${missing.join(', ')}`, diff --git a/electron/vite.config.electron.ts b/electron/vite.config.electron.ts index 02b08a95..042f09bf 100644 --- a/electron/vite.config.electron.ts +++ b/electron/vite.config.electron.ts @@ -1,6 +1,10 @@ +import path from 'path'; import { defineConfig } from 'vite'; import solid from 'vite-plugin-solid'; +const rootDir = path.resolve(process.cwd()); +const parentDir = path.resolve(rootDir, '..'); + export default defineConfig({ base: './', plugins: [solid()], @@ -8,5 +12,11 @@ export default defineConfig({ server: { port: 1421, strictPort: true, + watch: { + ignored: (watchedPath) => { + const resolvedPath = path.resolve(watchedPath); + return resolvedPath.startsWith(parentDir) && !resolvedPath.startsWith(rootDir); + }, + }, }, }); diff --git a/src/store/tasks.ts b/src/store/tasks.ts index 2a718e73..3b1c13a1 100644 --- a/src/store/tasks.ts +++ b/src/store/tasks.ts @@ -2,6 +2,7 @@ import { produce } from 'solid-js/store'; import { invoke, Channel } from '../lib/ipc'; import { IPC } from '../../electron/ipc/channels'; import { store, setStore, cleanupPanelEntries } from './core'; +import { saveState } from './persistence'; import { setTaskFocusedPanel } from './focus'; import { getProject, getProjectPath, getProjectBranchPrefix, isProjectMissing } from './projects'; import { setPendingShellCommand } from '../lib/bookmarks'; @@ -181,6 +182,7 @@ export async function createTask(opts: CreateTaskOptions): Promise { }; initTaskInStore(taskId, task, agent, projectId, agentDef); + saveState(); // fire-and-forget — errors handled internally return taskId; }