Skip to content

Troubleshooting

For rich text editors (TipTap, ProseMirror, CKEditor, Quill), make sure you’re targeting the actual editable element, not a wrapper:

// Wrong: targeting a container div
new WriteTrack({ target: document.querySelector('.editor')! });
// Right: targeting the actual contenteditable element
new 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.


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.

ScoreQuality Level
>= 0.9EXCELLENT
>= 0.7GOOD
>= 0.4FAIR
< 0.4POOR

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.


Swipe/gesture typing produces unusual patterns — fewer distinct keydown/keyup events and different timing. This may result in FAIR quality or unexpected analysis results.


The hook/composable won’t work if the ref isn’t attached to a DOM element:

// Wrong: ref not assigned to element
const { tracker } = useWriteTrack(textareaRef);
return <textarea />; // Missing ref={textareaRef}
// Right
return <textarea ref={textareaRef} />;

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.

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’s dev server pre-bundles dependencies, which breaks import.meta.url-based WASM resolution. Add this to your Vite config:

vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ['writetrack'],
},
});

This is only needed for dev mode — Vite production builds handle WASM correctly.

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.


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().