Skip to content

Commit 47b76ee

Browse files
joshdoejohannesjo
andauthored
Fix task creation reload in dev (#17)
* 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. * 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. * 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 --------- Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
1 parent 8f58dd4 commit 47b76ee

File tree

4 files changed

+26
-10
lines changed

4 files changed

+26
-10
lines changed

electron/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ function verifyPreloadAllowlist(): void {
5252
const preloadPath = path.join(__dirname, '..', 'electron', 'preload.cjs');
5353
const preloadSrc = fs.readFileSync(preloadPath, 'utf8');
5454
const enumValues = new Set(Object.values(IPC));
55-
const missing = [...enumValues].filter((v) => !preloadSrc.includes(`"${v}"`));
55+
const hasChannel = (channel: string) =>
56+
preloadSrc.includes(`'${channel}'`) || preloadSrc.includes(`"${channel}"`);
57+
const missing = [...enumValues].filter((v) => !hasChannel(v));
5658
if (missing.length > 0) {
5759
console.warn(
5860
`[preload-sync] IPC channels missing from preload.cjs ALLOWED_CHANNELS: ${missing.join(', ')}`,

electron/vite.config.electron.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1+
import path from 'path';
12
import { defineConfig } from 'vite';
23
import solid from 'vite-plugin-solid';
34

5+
const rootDir = path.resolve(process.cwd());
6+
const parentDir = path.resolve(rootDir, '..');
7+
48
export default defineConfig({
59
base: './',
610
plugins: [solid()],
711
clearScreen: false,
812
server: {
913
port: 1421,
1014
strictPort: true,
15+
watch: {
16+
ignored: (watchedPath) => {
17+
const resolvedPath = path.resolve(watchedPath);
18+
return resolvedPath.startsWith(parentDir) && !resolvedPath.startsWith(rootDir);
19+
},
20+
},
1121
},
1222
});

src/components/TaskStepsSection.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ export function TaskStepsSection(props: TaskStepsSectionProps) {
284284
flex: '1',
285285
}}
286286
>
287-
{truncate(step.summary, 140)}
287+
{truncate(step.summary ?? '', 140)}
288288
</span>
289289
</div>
290290

@@ -303,7 +303,11 @@ export function TaskStepsSection(props: TaskStepsSectionProps) {
303303
{truncate(step.detail ?? '', 280)}
304304
</div>
305305
</Show>
306-
<Show when={step.files_touched && step.files_touched.length > 0}>
306+
<Show
307+
when={
308+
Array.isArray(step.files_touched) && step.files_touched.length > 0
309+
}
310+
>
307311
<div
308312
style={{
309313
display: 'flex',
@@ -355,7 +359,7 @@ export function TaskStepsSection(props: TaskStepsSectionProps) {
355359
flex: '1',
356360
}}
357361
>
358-
{truncate(step().summary, 140)}
362+
{truncate(step().summary ?? '', 140)}
359363
</span>
360364
<Show when={step().timestamp}>
361365
<span

src/store/tasks.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { produce } from 'solid-js/store';
22
import { invoke, Channel } from '../lib/ipc';
33
import { IPC } from '../../electron/ipc/channels';
44
import { store, setStore, cleanupPanelEntries } from './core';
5+
import { saveState } from './persistence';
56
import { setTaskFocusedPanel } from './focus';
67
import { getProject, getProjectPath, getProjectBranchPrefix, isProjectMissing } from './projects';
78
import { setPendingShellCommand } from '../lib/bookmarks';
@@ -181,6 +182,7 @@ export async function createTask(opts: CreateTaskOptions): Promise<string> {
181182
};
182183

183184
initTaskInStore(taskId, task, agent, projectId, agentDef);
185+
saveState(); // fire-and-forget — errors handled internally
184186
return taskId;
185187
}
186188

@@ -609,12 +611,10 @@ export function setPlanContent(
609611
}
610612

611613
export function setStepsContent(taskId: string, steps: unknown[] | null): void {
612-
setStore(
613-
'tasks',
614-
taskId,
615-
'stepsContent',
616-
steps && steps.length > 0 ? (steps as StepEntry[]) : undefined,
617-
);
614+
const valid = steps
615+
? (steps.filter((s) => s !== null && typeof s === 'object' && !Array.isArray(s)) as StepEntry[])
616+
: [];
617+
setStore('tasks', taskId, 'stepsContent', valid.length > 0 ? valid : undefined);
618618
}
619619

620620
export function setTaskLastInputAt(taskId: string): void {

0 commit comments

Comments
 (0)