import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AppShell } from '@add-ideas/toolbox-shell-react'; import Toolbar from './components/Toolbar'; import EditorPanel from './components/EditorPanel'; import ActionDialog, { type ActionDialogAction, } from './components/ActionDialog'; import HelpDialog from './components/HelpDialog'; import SnippetToolbox from './components/SnippetToolbox'; import { useWorkbenchState } from './workspace/useWorkbenchState'; import type { WorkbenchDocumentKind } from './workspace/workspaceTypes'; import type { CodeMirrorEditorHandle } from './editor/editorTypes'; import { openTextFile, saveTextFile } from './file/fileService'; import { validateXml, formatXml } from './validation/xmlValidation'; import { validateXslt } from './validation/xsltValidation'; import { hasErrors, type DiagnosticMessage, } from './validation/validationTypes'; import { runTransformation } from './transform/transformService'; import { createTransformationRun } from './transform/nativeXsltEngine'; import { createSha256Hash } from './transform/hash'; import { isStylesheetExecutionTrusted, type TrustedStylesheet, } from './transform/stylesheetTrust'; import type { TransformEngineId } from './transform/transformTypes'; import { createApproximateTrace } from './transform/traceAnalyzer'; import { toolboxApp } from './toolboxApp'; interface DialogState { title: string; content: React.ReactNode; actions: ActionDialogAction[]; onCancel?: () => void; } const App: React.FC = () => { const { state, setState, updateDocumentText, replaceDocument, markDocumentSaved, setLastTransformation, resetWorkspace, } = useWorkbenchState(); const xmlInputEditorRef = useRef(null); const xsltEditorRef = useRef(null); const outputEditorRef = useRef(null); const [dialog, setDialog] = useState(null); const [helpOpen, setHelpOpen] = useState(false); const [busy, setBusy] = useState(false); const [statusMessage, setStatusMessage] = useState(null); const [runtimeDiagnostics, setRuntimeDiagnostics] = useState< DiagnosticMessage[] >([]); const [validationTouched, setValidationTouched] = useState(false); const [trustedStylesheet, setTrustedStylesheet] = useState(null); const stylesheetRevisionRef = useRef(0); const stateRef = useRef(state); useEffect(() => { stateRef.current = state; }, [state]); const invalidateStylesheetTrust = () => { stylesheetRevisionRef.current += 1; setTrustedStylesheet(null); }; const xmlDiagnostics = useMemo( () => validateXml(state.xmlInput.text, 'XML input'), [state.xmlInput.text] ); const xsltDiagnostics = useMemo( () => validateXslt(state.xsltCode.text, state.selectedEngine), [state.xsltCode.text, state.selectedEngine] ); const outputDiagnostics = useMemo(() => { if (!state.xmlOutput.text.trim()) return []; return validateXml(state.xmlOutput.text, 'XML output'); }, [state.xmlOutput.text]); const closeDialog = useCallback(() => { dialog?.onCancel?.(); setDialog(null); }, [dialog]); const askForConfirmation = useCallback( (options: { title: string; content: React.ReactNode; proceedLabel?: string; danger?: boolean; }): Promise => { return new Promise((resolve) => { const finish = (confirmed: boolean) => { setDialog(null); resolve(confirmed); }; setDialog({ title: options.title, content: options.content, onCancel: () => resolve(false), actions: [ { label: 'Cancel', variant: 'secondary', autoFocus: true, onClick: () => finish(false), }, { label: options.proceedLabel ?? 'Proceed', variant: options.danger ? 'danger' : 'primary', onClick: () => finish(true), }, ], }); }); }, [] ); const confirmOverwrite = useCallback( async (kind: WorkbenchDocumentKind, reason: string): Promise => { const documentState = state[kind]; if (!documentState.text.trim() && !documentState.dirty) return true; return askForConfirmation({ title: `Overwrite ${documentState.label}?`, content: (

{reason} This will replace the current contents of{' '} {documentState.label}.

), proceedLabel: 'Overwrite', danger: true, }); }, [askForConfirmation, state] ); const handleOpenHelp = useCallback(() => { setHelpOpen(true); }, []); const handleCloseHelp = useCallback(() => { setHelpOpen(false); }, []); const handleOpenFile = async (kind: WorkbenchDocumentKind) => { try { const opened = await openTextFile(kind); if (!opened) return; const documentState = state[kind]; if (documentState.dirty) { const mayProceed = await askForConfirmation({ title: `Overwrite ${documentState.label}?`, content: (

Opening {opened.name} will replace the current unsaved contents of {documentState.label}.

), proceedLabel: 'Overwrite', danger: true, }); if (!mayProceed) return; } replaceDocument(kind, opened.text, opened.name, false); if (kind === 'xsltCode') invalidateStylesheetTrust(); setStatusMessage(`Loaded ${opened.name}.`); } catch (error) { if (isAbortError(error)) return; setStatusMessage( error instanceof Error ? error.message : 'Failed to open file.' ); } }; const handleSaveFile = async (kind: WorkbenchDocumentKind) => { try { const documentState = state[kind]; const suggestedName = documentState.fileName ?? defaultFileName(kind); const savedName = await saveTextFile({ suggestedName, text: documentState.text, kind, }); if (savedName) { markDocumentSaved(kind, savedName); setStatusMessage(`Saved ${savedName}.`); } } catch (error) { if (isAbortError(error)) return; setStatusMessage( error instanceof Error ? error.message : 'Failed to save file.' ); } }; const handleApplyTransformation = async () => { setValidationTouched(true); setRuntimeDiagnostics([]); const diagnostics = [...xmlDiagnostics, ...xsltDiagnostics]; if (hasErrors(diagnostics)) { setRuntimeDiagnostics(diagnostics); setStatusMessage('Please fix validation errors before transforming.'); return; } if ( !trustedStylesheet || trustedStylesheet.revision !== stylesheetRevisionRef.current ) { setStatusMessage( 'Review and trust this exact stylesheet before transforming.' ); return; } if (state.options.askBeforeOverwritingOutput) { const mayOverwriteOutput = await confirmOverwrite( 'xmlOutput', 'Applying the transformation writes a new result.' ); if (!mayOverwriteOutput) return; } setBusy(true); try { const currentRevision = stylesheetRevisionRef.current; const currentState = stateRef.current; const currentHash = await createSha256Hash(currentState.xsltCode.text); if ( currentRevision !== stylesheetRevisionRef.current || !isStylesheetExecutionTrusted( trustedStylesheet, currentHash, currentRevision ) ) { setTrustedStylesheet(null); setStatusMessage( 'The stylesheet changed. Review and trust it again before transforming.' ); return; } const request = { xmlText: currentState.xmlInput.text, xsltText: currentState.xsltCode.text, engine: currentState.selectedEngine, }; const result = await runTransformation(request, { trustedStylesheet, currentRevision, }); setRuntimeDiagnostics(result.diagnostics); if (hasErrors(result.diagnostics)) { setStatusMessage( 'Transformation failed because the input or stylesheet is invalid.' ); return; } const output = currentState.options.prettifyOutputAfterTransform ? tryFormatXml(result.output) : result.output; replaceDocument( 'xmlOutput', output, currentState.xmlOutput.fileName, true ); const run = await createTransformationRun(request, output); setLastTransformation(run); setStatusMessage(`Transformation completed with ${result.engine}.`); } catch (error) { setRuntimeDiagnostics([ { severity: 'error', source: 'Transformation engine', message: error instanceof Error ? error.message : 'Transformation failed.', }, ]); setStatusMessage('Transformation failed.'); } finally { setBusy(false); } }; const handleTrustStylesheet = async () => { const revision = stylesheetRevisionRef.current; const stylesheetText = stateRef.current.xsltCode.text; try { const hash = await createSha256Hash(stylesheetText); const confirmed = await askForConfirmation({ title: 'Trust this executable stylesheet?', content: ( <>

XSLT is executable code. Both engines may access browser or network resources. Only continue if you trust the source of this exact stylesheet.

A trusted SaxonJS stylesheet may access data stored by other Toolbox apps on this same site, including PDF workspaces and portal preferences. It may send that data over the network or navigate this page away.

Size limits and a best-effort asynchronous deadline reduce accidental overload, but the engines do not provide complete process or network isolation. Synchronous work cannot be forcibly stopped. Any edit, opened replacement, formatting change, or reset revokes trust.

), proceedLabel: 'Trust this exact stylesheet', danger: true, }); if (!confirmed) { setStatusMessage('Stylesheet remains untrusted.'); return; } if ( revision !== stylesheetRevisionRef.current || stylesheetText !== stateRef.current.xsltCode.text ) { setTrustedStylesheet(null); setStatusMessage( 'The stylesheet changed during review. Review it again.' ); return; } setTrustedStylesheet({ hash, revision }); setStatusMessage( 'This exact stylesheet is trusted for the current session.' ); } catch (error) { setTrustedStylesheet(null); setStatusMessage( error instanceof Error ? error.message : 'Could not fingerprint the stylesheet.' ); } }; const handleMoveOutputToInput = async () => { if (!state.xmlOutput.text.trim()) { setStatusMessage('The output editor is empty. Nothing to move.'); return; } const mayProceed = await confirmOverwrite( 'xmlInput', 'Moving output to input is useful for chained transformations.' ); if (!mayProceed) return; replaceDocument('xmlInput', state.xmlOutput.text, 'from-output.xml', true); setStatusMessage('Moved output to XML input.'); }; const handleFormat = async (kind: WorkbenchDocumentKind) => { const documentState = state[kind]; try { const formatted = formatXml(documentState.text); if (formatted === documentState.text) { setStatusMessage(`${documentState.label} is already formatted.`); return; } const mayProceed = await askForConfirmation({ title: `Format ${documentState.label}?`, content: (

Formatting rewrites whitespace in{' '} {documentState.label}.

), proceedLabel: 'Format', }); if (!mayProceed) return; replaceDocument(kind, formatted, documentState.fileName, true); if (kind === 'xsltCode') invalidateStylesheetTrust(); setStatusMessage(`Formatted ${documentState.label}.`); } catch (error) { setStatusMessage( error instanceof Error ? error.message : 'Could not format XML.' ); } }; const handleValidate = () => { setValidationTouched(true); const allDiagnostics = [ ...xmlDiagnostics, ...xsltDiagnostics, ...outputDiagnostics, ]; setRuntimeDiagnostics(allDiagnostics); if (allDiagnostics.length === 0) { setStatusMessage('All current documents are well-formed.'); return; } setStatusMessage( hasErrors(allDiagnostics) ? 'Validation found errors.' : 'Validation completed with warnings.' ); }; const handleReset = async () => { const mayProceed = await askForConfirmation({ title: 'Reset workspace?', content: (

This restores the example XML and XSLT, clears the output, and removes the saved LocalStorage workspace.

), proceedLabel: 'Reset workspace', danger: true, }); if (!mayProceed) return; resetWorkspace(); invalidateStylesheetTrust(); setRuntimeDiagnostics([]); setValidationTouched(false); setStatusMessage('Workspace reset.'); }; const handleEngineChange = (engine: TransformEngineId) => { setState((current) => ({ ...current, selectedEngine: engine })); }; const handlePrettifyOutputAfterTransformChange = (enabled: boolean) => { setState((current) => ({ ...current, options: { ...current.options, prettifyOutputAfterTransform: enabled, }, })); }; const handleAskBeforeOverwritingOutputChange = (enabled: boolean) => { setState((current) => ({ ...current, options: { ...current.options, askBeforeOverwritingOutput: enabled, }, })); }; const traceItems = useMemo(() => { if (!state.xmlOutput.text.trim()) return []; return createApproximateTrace( state.xmlInput.text, state.xsltCode.text, state.xmlOutput.text ); }, [state.xmlInput.text, state.xsltCode.text, state.xmlOutput.text]); const displayedRuntimeDiagnostics = validationTouched ? runtimeDiagnostics : []; useEffect(() => { const handleGlobalKeyDown = (event: KeyboardEvent) => { if (event.defaultPrevented || event.repeat || event.isComposing) { return; } if (event.key === 'F1') { event.preventDefault(); event.stopPropagation(); handleOpenHelp(); return; } const isApplyShortcut = event.key === 'Enter' && event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey; if (isApplyShortcut) { event.preventDefault(); event.stopPropagation(); if (!busy) { if (trustedStylesheet) void handleApplyTransformation(); else void handleTrustStylesheet(); } } }; window.addEventListener('keydown', handleGlobalKeyDown, true); return () => { window.removeEventListener('keydown', handleGlobalKeyDown, true); }; }); return (
updateDocumentText('xmlInput', value)} onOpen={() => void handleOpenFile('xmlInput')} onSave={() => void handleSaveFile('xmlInput')} onUndo={() => xmlInputEditorRef.current?.undo()} onRedo={() => xmlInputEditorRef.current?.redo()} onCopy={() => void xmlInputEditorRef.current?.copySelection()} onCut={() => void xmlInputEditorRef.current?.cutSelection()} onPaste={() => void xmlInputEditorRef.current?.pasteText()} onPrettify={() => void handleFormat('xmlInput')} placeholderText="Paste or open XML here." /> { invalidateStylesheetTrust(); updateDocumentText('xsltCode', value); }} onOpen={() => void handleOpenFile('xsltCode')} onSave={() => void handleSaveFile('xsltCode')} onUndo={() => xsltEditorRef.current?.undo()} onRedo={() => xsltEditorRef.current?.redo()} onCopy={() => void xsltEditorRef.current?.copySelection()} onCut={() => void xsltEditorRef.current?.cutSelection()} onPaste={() => void xsltEditorRef.current?.pasteText()} onPrettify={() => void handleFormat('xsltCode')} placeholderText="Write or open XSLT here." actions={ xsltEditorRef.current?.insertText(snippet) } /> } /> updateDocumentText('xmlOutput', value)} onOpen={null} onSave={() => void handleSaveFile('xmlOutput')} onUndo={() => outputEditorRef.current?.undo()} onRedo={() => outputEditorRef.current?.redo()} onCopy={() => void outputEditorRef.current?.copySelection()} onCut={() => void outputEditorRef.current?.cutSelection()} onPaste={() => void outputEditorRef.current?.pasteText()} onPrettify={() => void handleFormat('xmlOutput')} placeholderText="Transformation output appears here." />
void handleApplyTransformation()} onTrustStylesheet={() => void handleTrustStylesheet()} onMoveOutputToInput={() => void handleMoveOutputToInput()} onValidate={handleValidate} onReset={() => void handleReset()} prettifyOutputAfterTransform={ state.options.prettifyOutputAfterTransform } onPrettifyOutputAfterTransformChange={ handlePrettifyOutputAfterTransformChange } askBeforeOverwritingOutput={state.options.askBeforeOverwritingOutput} onAskBeforeOverwritingOutputChange={ handleAskBeforeOverwritingOutputChange } busy={busy} stylesheetTrusted={trustedStylesheet !== null} /> {statusMessage &&
{statusMessage}
} {displayedRuntimeDiagnostics.length > 0 && (

Validation / runtime messages

    {displayedRuntimeDiagnostics.map((diagnostic, index) => (
  • {diagnostic.source} {diagnostic.message}
  • ))}
)}

Explain transformation

MVP approximation: result nodes are compared with simple stylesheet template matches. Full execution tracing requires an instrumented engine later.

{state.lastTransformation && ( Last run:{' '} {new Date( state.lastTransformation.transformedAt ).toLocaleString()} )}
{traceItems.length === 0 ? (

Run a transformation to see approximate result explanations.

) : (
{traceItems.map((item) => ( ))}
Result Likely source Likely template Confidence
{item.resultPath} {item.likelySourcePath ?? '—'} {item.likelyTemplate ?? '—'} {item.confidence}
)}
{dialog?.content} {helpOpen && }
); }; function tryFormatXml(text: string): string { try { return text.trim() ? formatXml(text) : text; } catch { return text; } } function defaultFileName(kind: WorkbenchDocumentKind): string { switch (kind) { case 'xmlInput': return 'input.xml'; case 'xsltCode': return 'transform.xsl'; case 'xmlOutput': return 'output.xml'; } } function isAbortError(error: unknown): boolean { return error instanceof DOMException && error.name === 'AbortError'; } export default App;