Skip to content

Commit 5a781ff

Browse files
committed
Merge branch 'task/make-the-review-plan-button-floating'
* task/make-the-review-plan-button-floating: fix(plan): remove opacity from floating Review Plan button fix(plan): add path validation, log errors, unexport internal function fix(plan): persist planFileName and restore exact file on restart fix(plan): restore plan content from disk on app restart feat(plan): float Review Plan button over inline plan and shrink dialog
2 parents bba36dd + 7505c3f commit 5a781ff

8 files changed

Lines changed: 70 additions & 29 deletions

File tree

electron/ipc/channels.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export enum IPC {
8080

8181
// Plan
8282
PlanContent = 'plan_content',
83+
ReadPlanContent = 'read_plan_content',
8384

8485
// Ask about code
8586
AskAboutCode = 'ask_about_code',

electron/ipc/plans.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,25 @@ export function stopPlanWatcher(taskId: string): void {
219219
watchers.delete(taskId);
220220
}
221221

222+
/** Read a specific plan file from a worktree, or the newest if no name given. */
223+
export function readPlanForWorktree(worktreePath: string, fileName?: string): { content: string; fileName: string } | null {
224+
const plansDirs = PLAN_DIRS.map((rel) => path.join(worktreePath, rel));
225+
226+
if (fileName) {
227+
for (const dir of plansDirs) {
228+
try {
229+
const content = fs.readFileSync(path.join(dir, fileName), 'utf-8');
230+
return { content, fileName };
231+
} catch {
232+
// Not in this directory
233+
}
234+
}
235+
return null;
236+
}
237+
238+
return readNewestPlanFromDirs(plansDirs);
239+
}
240+
222241
/** Stops all plan watchers. */
223242
export function stopAllPlanWatchers(): void {
224243
for (const taskId of watchers.keys()) {

electron/ipc/register.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
killAllAgents,
1414
getAgentMeta,
1515
} from './pty.js';
16-
import { ensurePlansDirectory, startPlanWatcher } from './plans.js';
16+
import { ensurePlansDirectory, startPlanWatcher, readPlanForWorktree } from './plans.js';
1717
import { startRemoteServer } from '../remote/server.js';
1818
import {
1919
getGitIgnoredDirs,
@@ -296,6 +296,14 @@ export function registerAllHandlers(win: BrowserWindow): void {
296296
return fs.existsSync(args.path);
297297
});
298298

299+
// --- Plan content (one-shot read) ---
300+
ipcMain.handle(IPC.ReadPlanContent, (_e, args) => {
301+
validatePath(args.worktreePath, 'worktreePath');
302+
const fileName = typeof args.fileName === 'string' ? args.fileName : undefined;
303+
if (fileName) validateRelativePath(fileName, 'fileName');
304+
return readPlanForWorktree(args.worktreePath, fileName);
305+
});
306+
299307
// --- Ask about code ---
300308
ipcMain.handle(IPC.AskAboutCode, (_e, args) => {
301309
assertString(args.requestId, 'requestId');

src/App.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,21 @@ function App() {
287287

288288
await loadAgents();
289289
await loadState();
290+
291+
// Restore plan content for tasks that had a plan file before restart
292+
for (const taskId of [...store.taskOrder, ...store.collapsedTaskOrder]) {
293+
const task = store.tasks[taskId];
294+
if (!task?.worktreePath || !task.planFileName) continue;
295+
invoke<{ content: string; fileName: string } | null>(IPC.ReadPlanContent, {
296+
worktreePath: task.worktreePath,
297+
fileName: task.planFileName,
298+
}).then((result) => {
299+
if (result) setPlanContent(taskId, result.content, result.fileName);
300+
}).catch((err) => {
301+
console.warn(`Failed to restore plan for task ${taskId}:`, err);
302+
});
303+
}
304+
290305
await validateProjectPaths();
291306
await restoreWindowState();
292307
await captureWindowState();

src/components/PlanViewerDialog.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ export function PlanViewerDialog(props: PlanViewerDialogProps) {
3939
<Dialog
4040
open={props.open}
4141
onClose={props.onClose}
42-
width="90vw"
42+
width="70vw"
4343
panelStyle={{
44-
height: '85vh',
45-
'max-width': '1400px',
44+
height: '70vh',
45+
'max-width': '1000px',
4646
overflow: 'hidden',
4747
padding: '0',
4848
gap: '0',

src/components/TaskPanel.tsx

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -617,34 +617,27 @@ export function TaskPanel(props: TaskPanelProps) {
617617
// eslint-disable-next-line solid/no-innerhtml -- plan files are local, written by Claude Code in the worktree
618618
innerHTML={planHtml()}
619619
/>
620-
<div
620+
<button
621+
ref={reviewPlanBtnRef}
622+
class="btn-secondary"
621623
style={{
622-
display: 'flex',
623-
'justify-content': 'center',
624-
padding: '6px 0',
625-
'flex-shrink': '0',
626-
'border-top': `1px solid ${theme.border}`,
627-
background: theme.taskPanelBg,
624+
position: 'absolute',
625+
bottom: '8px',
626+
right: '8px',
627+
padding: '4px 16px',
628+
'font-size': sf(11),
629+
'font-family': "'JetBrains Mono', monospace",
630+
background: theme.bgInput,
631+
color: theme.fgMuted,
632+
border: `1px solid ${theme.border}`,
633+
'border-radius': '6px',
634+
cursor: 'pointer',
635+
'z-index': '1',
628636
}}
637+
onClick={() => setPlanFullscreen(true)}
629638
>
630-
<button
631-
ref={reviewPlanBtnRef}
632-
class="btn-secondary"
633-
style={{
634-
padding: '4px 16px',
635-
'font-size': sf(11),
636-
'font-family': "'JetBrains Mono', monospace",
637-
background: theme.bgInput,
638-
color: theme.fgMuted,
639-
border: `1px solid ${theme.border}`,
640-
'border-radius': '6px',
641-
cursor: 'pointer',
642-
}}
643-
onClick={() => setPlanFullscreen(true)}
644-
>
645-
Review Plan
646-
</button>
647-
</div>
639+
Review Plan
640+
</button>
648641
</div>
649642
</Show>
650643
</div>

src/store/persistence.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export async function saveState(): Promise<void> {
6565
skipPermissions: task.skipPermissions,
6666
githubUrl: task.githubUrl,
6767
savedInitialPrompt: task.savedInitialPrompt,
68+
planFileName: task.planFileName,
6869
};
6970
}
7071

@@ -88,6 +89,7 @@ export async function saveState(): Promise<void> {
8889
skipPermissions: task.skipPermissions,
8990
githubUrl: task.githubUrl,
9091
savedInitialPrompt: task.savedInitialPrompt,
92+
planFileName: task.planFileName,
9193
collapsed: true,
9294
};
9395
}
@@ -334,6 +336,7 @@ export async function loadState(): Promise<void> {
334336
skipPermissions: pt.skipPermissions === true,
335337
githubUrl: pt.githubUrl,
336338
savedInitialPrompt: pt.savedInitialPrompt,
339+
planFileName: pt.planFileName,
337340
};
338341

339342
s.tasks[taskId] = task;
@@ -398,6 +401,7 @@ export async function loadState(): Promise<void> {
398401
skipPermissions: pt.skipPermissions === true,
399402
githubUrl: pt.githubUrl,
400403
savedInitialPrompt: pt.savedInitialPrompt,
404+
planFileName: pt.planFileName,
401405
collapsed: true,
402406
savedAgentDef: agentDef ?? undefined,
403407
};

src/store/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export interface PersistedTask {
7676
githubUrl?: string;
7777
savedInitialPrompt?: string;
7878
collapsed?: boolean;
79+
planFileName?: string;
7980
}
8081

8182
export interface PersistedTerminal {

0 commit comments

Comments
 (0)