Troubleshooting
Tracker Not Capturing Keystrokes
Section titled “Tracker Not Capturing Keystrokes”For rich text editors (TipTap, ProseMirror, CKEditor, Quill), make sure you’re targeting the actual editable element, not a wrapper:
// Wrong: targeting a container divnew WriteTrack({ target: document.querySelector('.editor')! });
// Right: targeting the actual contenteditable elementnew WriteTrack({ target: document.querySelector('.editor .ProseMirror')!,});Use the framework-specific bindings (useWriteTrack, WriteTrackExtension, etc.) to avoid this — they handle element targeting automatically.
If events still aren’t captured, check whether other code is calling event.stopPropagation() on keyboard events.
POOR Session Quality
Section titled “POOR Session Quality”Session quality is a composite score (0–1) based on four factors: whether keystroke events exist, whether text content exists, whether timestamps are valid, and whether the session lasted more than 1 second.
| Score | Quality Level |
|---|---|
| >= 0.9 | EXCELLENT |
| >= 0.7 | GOOD |
| >= 0.4 | FAIR |
| < 0.4 | POOR |
A session with many keystrokes can still be FAIR or POOR if timestamps are out of order or the session duration is under 1 second.
If the user pasted most of the content, there won’t be enough keystroke data for analysis. Check tracker.getClipboardEvents() to see if paste events dominate the session.
Mobile / Swipe Typing
Section titled “Mobile / Swipe Typing”Swipe/gesture typing produces unusual patterns — fewer distinct keydown/keyup events and different timing. This may result in FAIR quality or unexpected analysis results.
React / Vue Issues
Section titled “React / Vue Issues”Ref not attached
Section titled “Ref not attached”The hook/composable won’t work if the ref isn’t attached to a DOM element:
// Wrong: ref not assigned to elementconst { tracker } = useWriteTrack(textareaRef);return <textarea />; // Missing ref={textareaRef}
// Rightreturn <textarea ref={textareaRef} />;Component unmounted before getData
Section titled “Component unmounted before getData”Retrieve data before the component unmounts:
const handleSubmit = () => { if (tracker) { const data = tracker.getData(); // Call before unmount submitForm(data); }};WASM Loading / Analysis Returns Null {#wasm-loading}
Section titled “WASM Loading / Analysis Returns Null {#wasm-loading}”getAnalysis() returns null when the WASM module can’t be loaded. Check the browser console for a warning with details.
Most bundlers work by default
Section titled “Most bundlers work by default”Vite (production), webpack 5, Rollup, and Next.js all resolve the WASM file automatically. You shouldn’t need any configuration unless you see a console warning.
Vite dev mode
Section titled “Vite dev mode”Vite’s dev server pre-bundles dependencies, which breaks import.meta.url-based WASM resolution. Add this to your Vite config:
import { defineConfig } from 'vite';
export default defineConfig({ optimizeDeps: { exclude: ['writetrack'], },});This is only needed for dev mode — Vite production builds handle WASM correctly.
Custom WASM location
Section titled “Custom WASM location”If your bundler or hosting setup doesn’t serve the WASM file at the default URL, pass wasmUrl pointing to the file:
const tracker = new WriteTrack({ target: textarea, wasmUrl: '/static/writetrack.wasm',});Copy the WASM file from node_modules/writetrack/dist/writetrack.wasm to your public/static directory.
Content Security Policy
Section titled “Content Security Policy”If your site uses a strict CSP and you’re using the analysis module, you’ll need to allow WebAssembly compilation. Add wasm-unsafe-eval to your script-src directive:
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval';This is narrower than unsafe-eval — it only permits WebAssembly compilation, not eval() or new Function().