import type { EditorState } from '../richTextTypes' export interface EditorAction { type: 'command' | 'paste' | 'keyboard' | 'selection' | 'input' name: string timestamp: number details: { [key: string]: any } state: EditorStateSnapshot } export interface EditorStateSnapshot { content: string selection: { start: number end: number collapsed: boolean text: string } | null activeStyles: string[] caretPosition: { node: string // Node description offset: number } | null } export interface DebugSession { id: string startTime: number actions: EditorAction[] } export class EditorDebugger { private session: DebugSession private maxActions: number private isDevelopment: boolean constructor(maxActions = 1000) { this.maxActions = maxActions this.session = this.createNewSession() this.isDevelopment = import.meta.env.DEV } private createNewSession(): DebugSession { return { id: `session_${Date.now()}`, startTime: Date.now(), actions: [] } } private getNodeDescription(node: Node): string { if (node.nodeType === Node.TEXT_NODE) { return `Text("${node.textContent?.slice(0, 20)}${node.textContent && node.textContent.length > 20 ? '...' : ''}")` } const element = node as Element return `${element.tagName.toLowerCase()}${element.id ? `#${element.id}` : ''}` } private captureState(state: EditorState): EditorStateSnapshot { if (!state.doc || !state.selection) { return { content: '', selection: null, activeStyles: [], caretPosition: null } } const { selection } = state const range = selection.rangeCount ? selection.getRangeAt(0) : null return { content: state.doc.body.innerHTML, selection: range ? { start: range.startOffset, end: range.endOffset, collapsed: range.collapsed, text: range.toString() } : null, activeStyles: Array.from(state.selectedStyles), caretPosition: range ? { node: this.getNodeDescription(range.startContainer), offset: range.startOffset } : null } } logCommand(command: string, value: string | undefined, state: EditorState) { this.addAction({ type: 'command', name: command, timestamp: Date.now(), details: { value }, state: this.captureState(state) }) } logPaste(data: DataTransfer, state: EditorState) { this.addAction({ type: 'paste', name: 'paste', timestamp: Date.now(), details: { html: data.getData('text/html'), text: data.getData('text/plain'), types: Array.from(data.types) }, state: this.captureState(state) }) } logKeyboard(event: KeyboardEvent, state: EditorState) { this.addAction({ type: 'keyboard', name: 'keypress', timestamp: Date.now(), details: { key: event.key, code: event.code, ctrl: event.ctrlKey, alt: event.altKey, shift: event.shiftKey, meta: event.metaKey }, state: this.captureState(state) }) } logSelection(state: EditorState) { this.addAction({ type: 'selection', name: 'selection_change', timestamp: Date.now(), details: {}, state: this.captureState(state) }) } logInput(inputType: string, data: string | null, state: EditorState) { this.addAction({ type: 'input', name: inputType, timestamp: Date.now(), details: { data }, state: this.captureState(state) }) } private addAction(action: EditorAction) { this.session.actions.push(action) if (this.session.actions.length > this.maxActions) { this.session.actions.shift() } // Log to console in development if (this.isDevelopment) { console.log('Editor Action:', action) } } getSession(): DebugSession { return this.session } clearSession() { this.session = this.createNewSession() } exportSession(): string { return JSON.stringify(this.session, null, 2) } downloadSession() { const blob = new Blob([this.exportSession()], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `editor_session_${this.session.id}.json` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } exportSessionWithPrompt(userMessage?: string): string { const prompt = { message: userMessage || 'here is a debug log, can you analyze and fix accordingly?', session: this.session } return JSON.stringify(prompt, null, 2) } }