generated from nhcarrigan/template
bf411adeb7
## Summary This PR resolves several critical bugs that were blocking the permission modal and causing config loss: - **Permission modal not appearing** - Fixed z-index issues and runtime errors - **Config store race condition** - Resolved critical race condition causing settings to be lost - **Excessive logging** - Removed redundant fmt layer that was writing to hidden stdout - **System tool prompts** - Prevented unnecessary permission prompts for built-in tools - **Permission batching** - Added support for parallel permission requests - **ExitPlanMode tool** - Fixed ExitPlanMode tool not functioning correctly ## Changes Made ### Permission Modal Fixes - Updated z-index to proper value (9999) to ensure modal appears above all other UI elements - Fixed runtime errors that were preventing modal from rendering - Resolved issues with permission grants not being properly applied ### Config Store Race Condition - Fixed critical race condition where multiple rapid config updates would result in lost settings - Ensured config writes are properly sequenced to prevent data loss - Added proper synchronisation for config store operations ### Logging Cleanup - Removed redundant fmt formatting layer that was outputting to hidden stdout - Cleaned up excessive debug logging added during troubleshooting - Removed temporary debugging documentation files ### UX Improvements - Added close confirmation modal with minimise to tray option - Implemented batching for parallel permission requests - Added debug console for viewing frontend and backend logs ### ExitPlanMode Fix - Fixed ExitPlanMode tool not functioning correctly, ensuring proper transitions out of plan mode ## Issues Resolved Closes #112 - Permission flow now properly handles multiple tool requests Closes #113 - ExitPlanMode tool now functions correctly Closes #126 - Debug console feature added (partial - basic implementation complete) ## Test Plan - [x] Permission modal appears and functions correctly - [x] Config settings persist across app restarts - [x] No excessive logging in production builds - [x] System tools don't trigger permission prompts - [x] Parallel permission requests are properly batched - [x] Debug console displays frontend and backend logs - [x] ExitPlanMode properly exits plan mode --- ✨ This PR was created with help from Hikari~ 🌸 Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com> Reviewed-on: #127 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
155 lines
4.3 KiB
TypeScript
155 lines
4.3 KiB
TypeScript
import { writable, derived } from "svelte/store";
|
|
import { listen } from "@tauri-apps/api/event";
|
|
|
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
|
|
export interface LogEntry {
|
|
id: string;
|
|
timestamp: Date;
|
|
level: LogLevel;
|
|
message: string;
|
|
source: "frontend" | "backend";
|
|
}
|
|
|
|
interface DebugConsoleState {
|
|
logs: LogEntry[];
|
|
isOpen: boolean;
|
|
maxLogs: number;
|
|
filterLevel: LogLevel | "all";
|
|
autoScroll: boolean;
|
|
}
|
|
|
|
const MAX_LOGS = 1000; // Circular buffer size
|
|
|
|
function createDebugConsoleStore() {
|
|
const { subscribe, update } = writable<DebugConsoleState>({
|
|
logs: [],
|
|
isOpen: false,
|
|
maxLogs: MAX_LOGS,
|
|
filterLevel: "all",
|
|
autoScroll: true,
|
|
});
|
|
|
|
let logCounter = 0;
|
|
|
|
function addLog(level: LogLevel, message: string, source: "frontend" | "backend") {
|
|
update((state) => {
|
|
const newLog: LogEntry = {
|
|
id: `log-${Date.now()}-${logCounter++}`,
|
|
timestamp: new Date(),
|
|
level,
|
|
message,
|
|
source,
|
|
};
|
|
|
|
const updatedLogs = [...state.logs, newLog];
|
|
|
|
// Implement circular buffer - remove oldest if exceeding max
|
|
if (updatedLogs.length > state.maxLogs) {
|
|
updatedLogs.shift();
|
|
}
|
|
|
|
return { ...state, logs: updatedLogs };
|
|
});
|
|
}
|
|
|
|
// Override console methods to capture frontend logs
|
|
const originalConsole = {
|
|
log: console.log,
|
|
info: console.info,
|
|
warn: console.warn,
|
|
error: console.error,
|
|
debug: console.debug,
|
|
};
|
|
|
|
function setupConsoleCapture() {
|
|
console.log = (...args: unknown[]) => {
|
|
originalConsole.log(...args);
|
|
addLog("info", args.map((arg) => String(arg)).join(" "), "frontend");
|
|
};
|
|
|
|
console.info = (...args: unknown[]) => {
|
|
originalConsole.info(...args);
|
|
addLog("info", args.map((arg) => String(arg)).join(" "), "frontend");
|
|
};
|
|
|
|
console.warn = (...args: unknown[]) => {
|
|
originalConsole.warn(...args);
|
|
addLog("warn", args.map((arg) => String(arg)).join(" "), "frontend");
|
|
};
|
|
|
|
console.error = (...args: unknown[]) => {
|
|
originalConsole.error(...args);
|
|
addLog("error", args.map((arg) => String(arg)).join(" "), "frontend");
|
|
};
|
|
|
|
console.debug = (...args: unknown[]) => {
|
|
originalConsole.debug(...args);
|
|
addLog("debug", args.map((arg) => String(arg)).join(" "), "frontend");
|
|
};
|
|
|
|
// Capture unhandled errors
|
|
window.addEventListener("error", (event) => {
|
|
addLog(
|
|
"error",
|
|
`[Unhandled Error] ${event.message} at ${event.filename}:${event.lineno}:${event.colno}`,
|
|
"frontend"
|
|
);
|
|
});
|
|
|
|
// Capture unhandled promise rejections
|
|
window.addEventListener("unhandledrejection", (event) => {
|
|
addLog("error", `[Unhandled Promise Rejection] ${event.reason}`, "frontend");
|
|
});
|
|
}
|
|
|
|
function restoreConsole() {
|
|
console.log = originalConsole.log;
|
|
console.info = originalConsole.info;
|
|
console.warn = originalConsole.warn;
|
|
console.error = originalConsole.error;
|
|
console.debug = originalConsole.debug;
|
|
}
|
|
|
|
// Listen for backend logs
|
|
async function setupBackendLogsListener() {
|
|
await listen<{ level: LogLevel; message: string }>("debug:log", (event) => {
|
|
addLog(event.payload.level, event.payload.message, "backend");
|
|
});
|
|
}
|
|
|
|
return {
|
|
subscribe,
|
|
toggle: () => update((state) => ({ ...state, isOpen: !state.isOpen })),
|
|
open: () => update((state) => ({ ...state, isOpen: true })),
|
|
close: () => update((state) => ({ ...state, isOpen: false })),
|
|
clear: () => update((state) => ({ ...state, logs: [] })),
|
|
setFilterLevel: (level: LogLevel | "all") =>
|
|
update((state) => ({ ...state, filterLevel: level })),
|
|
setAutoScroll: (enabled: boolean) => update((state) => ({ ...state, autoScroll: enabled })),
|
|
setupConsoleCapture,
|
|
restoreConsole,
|
|
setupBackendLogsListener,
|
|
};
|
|
}
|
|
|
|
export const debugConsoleStore = createDebugConsoleStore();
|
|
|
|
// Derived store for filtered logs
|
|
export const filteredLogs = derived(debugConsoleStore, ($debugConsole) => {
|
|
if ($debugConsole.filterLevel === "all") {
|
|
return $debugConsole.logs;
|
|
}
|
|
|
|
const levelPriority: Record<LogLevel, number> = {
|
|
debug: 0,
|
|
info: 1,
|
|
warn: 2,
|
|
error: 3,
|
|
};
|
|
|
|
const minPriority = levelPriority[$debugConsole.filterLevel];
|
|
|
|
return $debugConsole.logs.filter((log) => levelPriority[log.level] >= minPriority);
|
|
});
|