mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
feat: add working directory deletion option to quit confirmation modal
Add agent name input with confirmation pattern to enable a "Quit & Delete Working Dirs" button. Widen modal, reduce button size/font, prevent wrapping. Guard against window.maestro timing issues in production.
This commit is contained in:
@@ -851,6 +851,12 @@ function MaestroConsoleInner() {
|
|||||||
window.maestro.app.confirmQuit();
|
window.maestro.app.confirmQuit();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleConfirmQuitAndDelete = useCallback(() => {
|
||||||
|
setQuitConfirmModalOpen(false);
|
||||||
|
// TODO: Implement working directory deletion before quit
|
||||||
|
window.maestro.app.confirmQuit();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleCancelQuit = useCallback(() => {
|
const handleCancelQuit = useCallback(() => {
|
||||||
setQuitConfirmModalOpen(false);
|
setQuitConfirmModalOpen(false);
|
||||||
window.maestro.app.cancelQuit();
|
window.maestro.app.cancelQuit();
|
||||||
@@ -4135,6 +4141,10 @@ function MaestroConsoleInner() {
|
|||||||
|
|
||||||
// Quit confirmation handler - shows modal when trying to quit with busy agents
|
// Quit confirmation handler - shows modal when trying to quit with busy agents
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Guard against window.maestro not being defined yet (production timing)
|
||||||
|
if (!window.maestro?.app?.onQuitConfirmationRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const unsubscribe = window.maestro.app.onQuitConfirmationRequest(() => {
|
const unsubscribe = window.maestro.app.onQuitConfirmationRequest(() => {
|
||||||
// Get all busy AI sessions (agents that are actively thinking)
|
// Get all busy AI sessions (agents that are actively thinking)
|
||||||
const busyAgents = sessions.filter(
|
const busyAgents = sessions.filter(
|
||||||
@@ -13518,6 +13528,7 @@ You are taking over this conversation. Based on the context above, provide a bri
|
|||||||
onCloseConfirmModal={handleCloseConfirmModal}
|
onCloseConfirmModal={handleCloseConfirmModal}
|
||||||
quitConfirmModalOpen={quitConfirmModalOpen}
|
quitConfirmModalOpen={quitConfirmModalOpen}
|
||||||
onConfirmQuit={handleConfirmQuit}
|
onConfirmQuit={handleConfirmQuit}
|
||||||
|
onConfirmQuitAndDelete={handleConfirmQuitAndDelete}
|
||||||
onCancelQuit={handleCancelQuit}
|
onCancelQuit={handleCancelQuit}
|
||||||
// AppSessionModals props
|
// AppSessionModals props
|
||||||
newInstanceModalOpen={newInstanceModalOpen}
|
newInstanceModalOpen={newInstanceModalOpen}
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ export interface AppConfirmModalsProps {
|
|||||||
// Quit Confirm Modal
|
// Quit Confirm Modal
|
||||||
quitConfirmModalOpen: boolean;
|
quitConfirmModalOpen: boolean;
|
||||||
onConfirmQuit: () => void;
|
onConfirmQuit: () => void;
|
||||||
|
onConfirmQuitAndDelete: () => void;
|
||||||
onCancelQuit: () => void;
|
onCancelQuit: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,6 +313,7 @@ export function AppConfirmModals({
|
|||||||
// Quit Confirm Modal
|
// Quit Confirm Modal
|
||||||
quitConfirmModalOpen,
|
quitConfirmModalOpen,
|
||||||
onConfirmQuit,
|
onConfirmQuit,
|
||||||
|
onConfirmQuitAndDelete,
|
||||||
onCancelQuit,
|
onCancelQuit,
|
||||||
}: AppConfirmModalsProps) {
|
}: AppConfirmModalsProps) {
|
||||||
// Compute busy agents for QuitConfirmModal
|
// Compute busy agents for QuitConfirmModal
|
||||||
@@ -338,6 +340,7 @@ export function AppConfirmModals({
|
|||||||
busyAgentCount={busyAgents.length}
|
busyAgentCount={busyAgents.length}
|
||||||
busyAgentNames={busyAgents.map((s) => s.name)}
|
busyAgentNames={busyAgents.map((s) => s.name)}
|
||||||
onConfirmQuit={onConfirmQuit}
|
onConfirmQuit={onConfirmQuit}
|
||||||
|
onConfirmQuitAndDelete={onConfirmQuitAndDelete}
|
||||||
onCancel={onCancelQuit}
|
onCancel={onCancelQuit}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -1742,6 +1745,7 @@ export interface AppModalsProps {
|
|||||||
onCloseConfirmModal: () => void;
|
onCloseConfirmModal: () => void;
|
||||||
quitConfirmModalOpen: boolean;
|
quitConfirmModalOpen: boolean;
|
||||||
onConfirmQuit: () => void;
|
onConfirmQuit: () => void;
|
||||||
|
onConfirmQuitAndDelete: () => void;
|
||||||
onCancelQuit: () => void;
|
onCancelQuit: () => void;
|
||||||
|
|
||||||
// --- AppSessionModals props ---
|
// --- AppSessionModals props ---
|
||||||
@@ -2097,6 +2101,7 @@ export function AppModals(props: AppModalsProps) {
|
|||||||
onCloseConfirmModal,
|
onCloseConfirmModal,
|
||||||
quitConfirmModalOpen,
|
quitConfirmModalOpen,
|
||||||
onConfirmQuit,
|
onConfirmQuit,
|
||||||
|
onConfirmQuitAndDelete,
|
||||||
onCancelQuit,
|
onCancelQuit,
|
||||||
// Session modals
|
// Session modals
|
||||||
newInstanceModalOpen,
|
newInstanceModalOpen,
|
||||||
@@ -2369,6 +2374,7 @@ export function AppModals(props: AppModalsProps) {
|
|||||||
onCloseConfirmModal={onCloseConfirmModal}
|
onCloseConfirmModal={onCloseConfirmModal}
|
||||||
quitConfirmModalOpen={quitConfirmModalOpen}
|
quitConfirmModalOpen={quitConfirmModalOpen}
|
||||||
onConfirmQuit={onConfirmQuit}
|
onConfirmQuit={onConfirmQuit}
|
||||||
|
onConfirmQuitAndDelete={onConfirmQuitAndDelete}
|
||||||
onCancelQuit={onCancelQuit}
|
onCancelQuit={onCancelQuit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* Focus defaults to Cancel to prevent accidental data loss.
|
* Focus defaults to Cancel to prevent accidental data loss.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle } from 'lucide-react';
|
||||||
import type { Theme } from '../types';
|
import type { Theme } from '../types';
|
||||||
import { useLayerStack } from '../contexts/LayerStackContext';
|
import { useLayerStack } from '../contexts/LayerStackContext';
|
||||||
@@ -20,6 +20,8 @@ interface QuitConfirmModalProps {
|
|||||||
busyAgentNames: string[];
|
busyAgentNames: string[];
|
||||||
/** Callback when user confirms quit */
|
/** Callback when user confirms quit */
|
||||||
onConfirmQuit: () => void;
|
onConfirmQuit: () => void;
|
||||||
|
/** Callback when user confirms quit and requests working directory deletion */
|
||||||
|
onConfirmQuitAndDelete: () => void;
|
||||||
/** Callback when user cancels (stays in app) */
|
/** Callback when user cancels (stays in app) */
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
@@ -35,6 +37,7 @@ export function QuitConfirmModal({
|
|||||||
busyAgentCount,
|
busyAgentCount,
|
||||||
busyAgentNames,
|
busyAgentNames,
|
||||||
onConfirmQuit,
|
onConfirmQuit,
|
||||||
|
onConfirmQuitAndDelete,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: QuitConfirmModalProps): JSX.Element {
|
}: QuitConfirmModalProps): JSX.Element {
|
||||||
const { registerLayer, unregisterLayer, updateLayerHandler } = useLayerStack();
|
const { registerLayer, unregisterLayer, updateLayerHandler } = useLayerStack();
|
||||||
@@ -42,6 +45,12 @@ export function QuitConfirmModal({
|
|||||||
const cancelButtonRef = useRef<HTMLButtonElement>(null);
|
const cancelButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
const onCancelRef = useRef(onCancel);
|
const onCancelRef = useRef(onCancel);
|
||||||
onCancelRef.current = onCancel;
|
onCancelRef.current = onCancel;
|
||||||
|
const [confirmName, setConfirmName] = useState('');
|
||||||
|
|
||||||
|
// Check if typed name matches any busy agent name (case-insensitive)
|
||||||
|
const deleteEnabled = busyAgentNames.some(
|
||||||
|
(name) => name.toLowerCase() === confirmName.trim().toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
// Focus Cancel button on mount (safer default action)
|
// Focus Cancel button on mount (safer default action)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -98,7 +107,7 @@ export function QuitConfirmModal({
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="w-[450px] border rounded-xl shadow-2xl overflow-hidden"
|
className="w-[520px] border rounded-xl shadow-2xl overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.colors.bgSidebar,
|
backgroundColor: theme.colors.bgSidebar,
|
||||||
borderColor: theme.colors.border,
|
borderColor: theme.colors.border,
|
||||||
@@ -170,11 +179,47 @@ export function QuitConfirmModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Agent name confirmation input for working directory deletion */}
|
||||||
|
<div className="mt-4">
|
||||||
|
<label
|
||||||
|
className="block text-xs mb-1.5"
|
||||||
|
style={{ color: theme.colors.textDim }}
|
||||||
|
htmlFor="quit-confirm-agent-name"
|
||||||
|
>
|
||||||
|
Enter agent name below to enable working directory deletion
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="quit-confirm-agent-name"
|
||||||
|
type="text"
|
||||||
|
value={confirmName}
|
||||||
|
onChange={(e) => setConfirmName(e.target.value)}
|
||||||
|
placeholder=""
|
||||||
|
className="w-full px-3 py-1.5 rounded-lg border text-xs outline-none focus:ring-1"
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.colors.bgMain,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
color: theme.colors.textMain,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="mt-6 flex justify-end gap-3">
|
<div className="mt-5 flex items-center justify-end gap-2 flex-nowrap">
|
||||||
|
<button
|
||||||
|
onClick={onConfirmQuitAndDelete}
|
||||||
|
disabled={!deleteEnabled}
|
||||||
|
className="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors whitespace-nowrap"
|
||||||
|
style={{
|
||||||
|
backgroundColor: deleteEnabled ? theme.colors.error : `${theme.colors.error}40`,
|
||||||
|
color: deleteEnabled ? '#ffffff' : `#ffffff80`,
|
||||||
|
cursor: deleteEnabled ? 'pointer' : 'not-allowed',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Quit & Delete Working Dirs
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onConfirmQuit}
|
onClick={onConfirmQuit}
|
||||||
className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-90"
|
className="px-3 py-1.5 rounded-lg text-xs font-medium transition-colors hover:opacity-90 whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.colors.error,
|
backgroundColor: theme.colors.error,
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
@@ -185,7 +230,7 @@ export function QuitConfirmModal({
|
|||||||
<button
|
<button
|
||||||
ref={cancelButtonRef}
|
ref={cancelButtonRef}
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="px-4 py-2 rounded-lg text-sm font-medium outline-none focus:ring-2 focus:ring-offset-1 transition-colors"
|
className="px-3 py-1.5 rounded-lg text-xs font-medium outline-none focus:ring-2 focus:ring-offset-1 transition-colors whitespace-nowrap"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme.colors.accent,
|
backgroundColor: theme.colors.accent,
|
||||||
color: theme.colors.accentForeground,
|
color: theme.colors.accentForeground,
|
||||||
|
|||||||
@@ -1815,6 +1815,10 @@ export function useSettings(): UseSettingsReturn {
|
|||||||
// Reload settings when system resumes from sleep/suspend
|
// Reload settings when system resumes from sleep/suspend
|
||||||
// This ensures settings like maxOutputLines aren't reset to defaults
|
// This ensures settings like maxOutputLines aren't reset to defaults
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Guard against window.maestro not being defined yet (production timing)
|
||||||
|
if (!window.maestro?.app?.onSystemResume) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const cleanup = window.maestro.app.onSystemResume(() => {
|
const cleanup = window.maestro.app.onSystemResume(() => {
|
||||||
console.log('[Settings] System resumed from sleep, reloading settings');
|
console.log('[Settings] System resumed from sleep, reloading settings');
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|||||||
Reference in New Issue
Block a user