API Reference
Complete reference for all public exports from the WriteTrack SDK.
Import Paths
Section titled “Import Paths”WriteTrack provides multiple entry points via package.json exports:
| Import Path | Format | Description |
|---|---|---|
writetrack | ESM, CJS, Browser | Core SDK — WriteTrack class and types |
writetrack/pipes | ESM, CJS, Browser | Output sink factories (also re-exported from writetrack) |
writetrack/browser | ESM | Browser bundle (same as core, explicit browser entry) |
Frameworks
Section titled “Frameworks”| Import Path | Format | Description |
|---|---|---|
writetrack/react | ESM | React hook — useWriteTrack |
writetrack/vue | ESM | Vue composable — useWriteTrack |
Rich-Text Editors
Section titled “Rich-Text Editors”| Import Path | Format | Description |
|---|---|---|
writetrack/tiptap | ESM | TipTap extension — WriteTrackExtension |
writetrack/ckeditor | ESM | CKEditor 5 plugin — WriteTrackPlugin |
writetrack/prosemirror | ESM | ProseMirror plugin — WriteTrackPlugin |
writetrack/quill | ESM | Quill module — WriteTrackModule |
writetrack/lexical | ESM | Lexical integration — createWriteTrackLexical |
writetrack/slate | ESM | Slate integration — createWriteTrackSlate |
writetrack/tinymce | ESM | TinyMCE native plugin — side-effect import registers with PluginManager |
Utilities
Section titled “Utilities”| Import Path | Format | Description |
|---|---|---|
writetrack/viz | ESM | Session visualization components |
writetrack/testing | ESM | Test factories — createMockAnalysis, createMockTracker |
ESM (recommended)
Section titled “ESM (recommended)”import { WriteTrack, webhook, datadog, segment, opentelemetry, analyzeEvents, formatIndicator,} from 'writetrack';import { useWriteTrack } from 'writetrack/react';import { useWriteTrack } from 'writetrack/vue';import { WriteTrackExtension } from 'writetrack/tiptap';import { WriteTrackPlugin } from 'writetrack/ckeditor';import { WriteTrackPlugin } from 'writetrack/prosemirror';import { WriteTrackModule } from 'writetrack/quill';import { createWriteTrackLexical } from 'writetrack/lexical';import { createWriteTrackSlate } from 'writetrack/slate';import 'writetrack/tinymce'; // registers TinyMCE plugin via side effectimport { verifyAnalysisSignatureAsync } from 'writetrack/verify';import { WtScorecard } from 'writetrack/viz';CommonJS
Section titled “CommonJS”const { WriteTrack, webhook } = require('writetrack');All types are exported from their respective entry points:
import type { WriteTrackOptions, WriteTrackDataSchema, SessionAnalysis, SessionReport, IndicatorOutput, KeystrokeEvent, ClipboardEvent, SelectionEvent, UndoRedoEvent, ProgrammaticInsertionEvent, CompositionEvent, InputSource, WriteTrackSink, WebhookOptions, DatadogOptions, SegmentOptions, OpenTelemetryOptions,} from 'writetrack';Choosing between WriteTrack and analyzeEvents()
Section titled “Choosing between WriteTrack and analyzeEvents()”WriteTrack has two entry points with distinct purposes. Pick one by where analysis runs.
new WriteTrack(options) | analyzeEvents(data, options) | |
|---|---|---|
| Runs | Client-side, in the browser | Server-side, in Node |
| Input | An HTMLElement to instrument | A captured WriteTrackDataSchema session |
| Output | Continuous capture + on-device analysis via getAnalysis() / getSessionReport() | A single SessionAnalysis for the provided session |
| Options type | WriteTrackOptions — target, license, event callbacks, persistence, editor hooks | AnalyzeEventsOptions — license, wasmUrl, errors behaviour |
| Use when | You’re embedding the tracker in a page and want live signals | You’ve already captured a session and want to analyze it off the client |
Both shapes accept the license key on a field named license.
For the end-to-end path when the two meet (analyze on the server, render on the client), see Server → client → scorecard.
WriteTrack Class
Section titled “WriteTrack Class”The main class for keystroke tracking and behavioral analysis.
Constructor
Section titled “Constructor”new WriteTrack(optionsOrTarget: WriteTrackOptions | HTMLElement)Creates a new WriteTrack instance. Accepts either a full options object or an HTMLElement directly for localhost evaluation.
| Parameter | Type | Description |
|---|---|---|
optionsOrTarget | WriteTrackOptions | HTMLElement | Configuration options or target element |
// Full options with contextconst tracker = new WriteTrack({ target: document.querySelector('#response-field'), license: 'wt_live_...', userId: 'u_abc123', contentId: 'post_draft_42', metadata: { formName: 'signup' },});
// Shorthand (localhost evaluation)const tracker = new WriteTrack(document.querySelector('#response-field'));start()
Section titled “start()”start(): voidBegins recording keystroke events. Clears any previously captured data.
- Resets all event arrays (keystrokes, clipboard, selection, undo/redo, programmatic insertion, composition)
- Records session start time
- Enables event capture
const tracker = new WriteTrack({ target: textarea });tracker.start(); // Now recording
// User types...
const events = tracker.getRawEvents();stop()
Section titled “stop()”stop(): voidStops recording keystrokes.
- Disables event capture
- Cleans up selection polling interval and mutation observer
- Data remains available after stopping
tracker.stop();// Data still accessibleconst events = tracker.getRawEvents();stopAndWait()
Section titled “stopAndWait()”async stopAndWait(): Promise<void>Stops recording and waits for any pending IndexedDB save to complete. Use this instead of stop() when you need to delete persisted data or navigate away immediately after stopping. When persist is not enabled, behaves identically to stop().
await tracker.stopAndWait();await tracker.clearPersistedData(); // Safe — save already flushedgetData()
Section titled “getData()”getData(): WriteTrackDataSchemaReturns the complete session as a structured WriteTrackDataSchema object, including metadata, all captured events, and computed quality metrics.
tracker.stop();const data = tracker.getData();// {// version: "2.1.0",// metadata: { tool, targetElement, timestamp, duration },// session: { events, clipboardEvents, selectionEvents, compositionEvents, ... },// quality: { overallScore, sequenceValid, qualityLevel, ... },// }
// Send to your serverawait fetch('/api/submit', { method: 'POST', body: JSON.stringify(data),});Example return value
{ "version": "2.0.0", "metadata": { "tool": { "name": "writetrack", "version": "0.11.0" }, "targetElement": "textarea", "timestamp": "2026-04-03T12:00:00.000Z", "duration": 45000, "userId": "user-123", "contentId": "response-1", "custom": { "assignmentId": "hw-5" }, "sessionId": "a1b2c3d4" }, "session": { "events": ["/* KeystrokeEvent[] */"], "clipboardEvents": ["/* ClipboardEvent[] */"], "rawText": "The user's final text...", "sessionStartTime": 1743681600000, "sessionEndTime": 1743681645000 }, "quality": { "overallScore": 0.92, "completeness": 98.5, "sequenceValid": true, "timingValid": true, "sessionDuration": 45000, "eventCount": 312, "qualityLevel": "EXCELLENT", "issues": [], "validatedAt": "2026-04-03T12:00:45.000Z" }}See WriteTrackDataSchema for full type definitions.
getText()
Section titled “getText()”getText(): stringReturns the current text content of the target element. Reads value for input/textarea elements, or textContent for other elements.
getRawEvents()
Section titled “getRawEvents()”getRawEvents(): KeystrokeEvent[]Returns a copy of all captured keystroke events.
const events = tracker.getRawEvents();console.log(`Captured ${events.length} keystrokes`);
events .filter((e) => e.type === 'keydown') .forEach((e) => { console.log(`${e.key} at ${e.timestamp}ms, flight: ${e.flightTime}ms`); });getClipboardEvents()
Section titled “getClipboardEvents()”getClipboardEvents(): ClipboardEvent[]Returns a copy of all clipboard events (copy, paste, cut).
const pastes = tracker.getClipboardEvents().filter((e) => e.type === 'paste');console.log(`User pasted ${pastes.length} times`);
pastes.forEach((e) => { console.log(`Pasted ${e.length} chars at position ${e.position}`);});getSelectionEvents()
Section titled “getSelectionEvents()”getSelectionEvents(): SelectionEvent[]Returns text selection events.
const selections = tracker.getSelectionEvents();selections.forEach((e) => { console.log(`Selected ${e.selectedLength} chars via ${e.method}`);});getUndoRedoEvents()
Section titled “getUndoRedoEvents()”getUndoRedoEvents(): UndoRedoEvent[]Returns undo/redo operation events.
getProgrammaticInsertionEvents()
Section titled “getProgrammaticInsertionEvents()”getProgrammaticInsertionEvents(): ProgrammaticInsertionEvent[]Returns detected programmatic insertion events. These are emitted when text appears in the input without corresponding keystrokes, indicating browser autocomplete, autofill, predictive text, AI extension injection, voice dictation, or programmatic value assignment.
const insertions = tracker.getProgrammaticInsertionEvents();insertions.forEach((e) => { console.log( `Programmatic insertion: "${e.insertedText}" (${e.insertedLength} chars)` );});getCompositionEvents()
Section titled “getCompositionEvents()”getCompositionEvents(): CompositionEvent[]Returns IME composition events captured during the session. A composition event is emitted when a CJK (Chinese, Japanese, Korean) input method completes a composition sequence. Intermediate keystrokes during composition are suppressed.
const compositions = tracker.getCompositionEvents();compositions.forEach((e) => { console.log(`Composition: ${e.insertedLength} chars in ${e.duration}ms`);});getSessionDuration()
Section titled “getSessionDuration()”getSessionDuration(): numberReturns the total session duration in milliseconds since start() was called, including time when the tab was hidden.
const duration = tracker.getSessionDuration();console.log(`Session: ${(duration / 1000).toFixed(1)} seconds`);getActiveTime()
Section titled “getActiveTime()”getActiveTime(): numberReturns the active (visible) session time in milliseconds. This subtracts time spent with the tab hidden from the total session duration. Returns 0 before start() is called.
const active = tracker.getActiveTime();const total = tracker.getSessionDuration();console.log( `Active: ${(active / 1000).toFixed(1)}s of ${(total / 1000).toFixed(1)}s total`);getKeystrokeCount()
Section titled “getKeystrokeCount()”getKeystrokeCount(): numberReturns the total number of keydown events captured.
pipe()
Section titled “pipe()”pipe(sink: WriteTrackSink): thisRegisters an output sink to receive session data when getData() is called. Returns this for chaining.
import { WriteTrack, webhook } from 'writetrack';
tracker .pipe(webhook({ url: 'https://api.example.com/typing' })) .pipe({ send: async (data) => console.log(data) });See Output Sinks for full documentation of the pipe system and available sinks.
tracker.on(event, handler, opts?);Registers an event listener. Returns an unsubscribe function. The analysis event accepts the optional opts.throttle (milliseconds) to control update frequency (default 1000ms).
Supported events:
| Event | Handler signature | Description |
|---|---|---|
ready | () => void | Fires when tracker is ready (persistence loaded). Late listeners auto-fire. |
change | (data: { eventCount: number; keystrokeCount: number }) => void | Fires on any data mutation (keystroke, paste, selection, etc.) |
analysis | (analysis: SessionAnalysis) => void | Fires when analysis changes (1s default throttle). Lazy-loads WASM on first listener. |
tick | (data: { activeTime: number; totalTime: number; tracker: WriteTrack }) => void | Fires every ~1s while session is active and tab is visible |
wasm:ready | () => void | Fires when WASM binary loads successfully. Late listeners auto-fire. |
wasm:error | (error: Error) => void | Fires if WASM binary fails to load |
stop | () => void | Fires when stop() is called |
pipe:error | (err: Error, sink: WriteTrackSink) => void | Fires when an output sink fails |
// Unsubscribe patternconst unsub = tracker.on('tick', ({ activeTime }) => { console.log(`Active: ${Math.floor(activeTime / 1000)}s`);});// Later: unsub();
// Analysis updates (WASM loads automatically on first listener)tracker.on('analysis', (analysis) => { console.log(`Indicator: ${analysis.contentOrigin.indicator.code}`);});
// Readinesstracker.on('ready', () => { console.log('Tracker ready');});tracker.off(event, handler);Removes a specific event listener. The easier pattern is to call the unsubscribe function returned by on().
removeAllListeners()
Section titled “removeAllListeners()”removeAllListeners(event?: string): voidRemoves all listeners for a given event, or all listeners if no event is specified.
isLicenseValid()
Section titled “isLicenseValid()”isLicenseValid(): booleanReturns whether a valid, non-expired license key has been verified for the current domain. Returns false when no license key is provided, during the grace period, or when validation fails. Recording (event capture) works regardless of license status — only getAnalysis() requires a valid license, grace period, or localhost.
isLicenseValidated()
Section titled “isLicenseValidated()”isLicenseValidated(): booleanReturns whether license validation has been performed.
if (tracker.isLicenseValidated() && tracker.isLicenseValid()) { console.log('License valid, ready to use');}isTargetDetached()
Section titled “isTargetDetached()”isTargetDetached(): booleanReturns whether the monitored target element has been removed from the DOM. Useful for detecting SPA navigation that destroys the tracked element.
if (tracker.isTargetDetached()) { console.warn('Target element removed — call stop() and create a new tracker');}ready: Promise<void>;A promise that resolves when the tracker is ready for use. When persist: true, this resolves after IndexedDB loads any prior session data. When persist: false, it resolves immediately. After stop() with persistence enabled, ready resolves when the save completes.
const tracker = new WriteTrack({ target: textarea, contentId: 'doc-1', persist: true,});await tracker.ready; // IndexedDB loadedtracker.start(); // Will auto-resume prior session if availableclearPersistedData()
Section titled “clearPersistedData()”async clearPersistedData(): Promise<void>Removes any persisted session data from IndexedDB for this field. No-op when persist is not enabled. Use this to reset a field’s session history.
await tracker.clearPersistedData();getAnalysis()
Section titled “getAnalysis()”async getAnalysis(): Promise<SessionAnalysis | null>Runs WASM-powered analysis on the captured session data. The WASM binary is lazy-loaded on the first call and cached for subsequent calls. Returns null if the WASM module fails to load.
For a combined bundle of session data and analysis in one call, use getSessionReport() instead.
const tracker = new WriteTrack({ target: textarea, wasmUrl: '/writetrack.wasm',});tracker.start();
// ... user types ...
tracker.stop();const analysis = await tracker.getAnalysis();// {// version: "0.9.1",// sufficientData: true,// contentOrigin: { ... },// timingAuthenticity: { ... },// ...// }getSessionReport()
Section titled “getSessionReport()”async getSessionReport(): Promise<SessionReport>Returns both the raw capture data and the WASM analysis in a single call.
This is a convenience method that combines getData() and getAnalysis() into a single SessionReport object. Use this when you need both the raw session data and the analysis together. For streaming data to external services, see output sinks (.pipe()) instead.
const report = await tracker.getSessionReport();// {// data: WriteTrackDataSchema, // Same as getData()// analysis: SessionAnalysis, // Same as getAnalysis()// }
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(report),});wasmReady
Section titled “wasmReady”get wasmReady(): booleanWhether the WASM module has been loaded and is ready for analysis. Returns false until getAnalysis() is called for the first time and the WASM binary loads successfully.
const tracker = new WriteTrack({ target: textarea });console.log(tracker.wasmReady); // false
await tracker.getAnalysis();console.log(tracker.wasmReady); // true (if WASM loaded)Static: listPersistedSessions()
Section titled “Static: listPersistedSessions()”static async listPersistedSessions(): Promise<PersistedSessionInfo[]>Returns metadata for all persisted sessions stored in IndexedDB. Useful for multi-document apps that need to display or manage saved writing sessions.
const sessions = await WriteTrack.listPersistedSessions();// [{ contentId: 'doc_1', fieldId: 'default', keystrokeCount: 42, lastSavedAt: 1709..., sessionId: '...' }]Static: deletePersistedSession()
Section titled “Static: deletePersistedSession()”static async deletePersistedSession(contentId: string, fieldId?: string): Promise<void>Deletes persisted session data from IndexedDB. If fieldId is omitted, deletes all sessions for the given contentId.
// Delete all sessions for a documentawait WriteTrack.deletePersistedSession('doc_1');
// Delete a specific field's sessionawait WriteTrack.deletePersistedSession('doc_1', 'response');Pipes Exports
Section titled “Pipes Exports”import { webhook, datadog, segment, opentelemetry } from 'writetrack';| Export | Returns | Description |
|---|---|---|
webhook(options) | WriteTrackSink | POST session data to a URL |
datadog(options) | WriteTrackSink | Send as Datadog RUM custom action |
segment(options) | WriteTrackSink | Send as Segment track event |
opentelemetry(options) | WriteTrackSink | Send as OpenTelemetry span |
See Output Sinks for configuration options and usage guides.
Standalone Exports
Section titled “Standalone Exports”analyzeEvents()
Section titled “analyzeEvents()”import { analyzeEvents } from 'writetrack';
type AnalyzeEventsInput = | WriteTrackDataSchema | { data: WriteTrackDataSchema; analysis?: unknown };
async function analyzeEvents( input: AnalyzeEventsInput, options?: { wasmUrl?: string; license?: string; errors?: 'return' | 'throw'; }): Promise<SessionAnalysis | null>;Analyze a WriteTrackDataSchema object without needing a live DOM element. Useful for server-side analysis of previously captured session data.
The input argument accepts either a raw WriteTrackDataSchema (the output of WriteTrack.getData()) or a wrapped { data, analysis? } report — the same shape createSessionReport() produces. Wrapped input is unwrapped automatically so stored reports can be re-analyzed without hand-unwrapping; any analysis field on the wrapper is ignored and the inner data is always re-analyzed.
// Server-side: analyze data received from the clientconst data = await request.json(); // WriteTrackDataSchema from getData()const analysis = await analyzeEvents(data, { license: process.env.WRITETRACK_LICENSE_KEY,});
// Or pass a stored SessionReport directly — analyzeEvents unwraps itconst storedReport = await db.loadReport(reportId); // { data, analysis }const freshAnalysis = await analyzeEvents(storedReport, { license: process.env.WRITETRACK_LICENSE_KEY,});Failure handling
Section titled “Failure handling”By default analyzeEvents() returns null for every failure mode — missing license, invalid input, WASM load failure, or analysis validation failure — which makes diagnosis painful for batch callers. Pass errors: 'throw' to receive typed errors instead:
import { analyzeEvents, LicenseError, InputValidationError, WasmLoadError, AnalysisError,} from 'writetrack';
try { const analysis = await analyzeEvents(data, { license: process.env.WRITETRACK_LICENSE_KEY, errors: 'throw', });} catch (e) { if (e instanceof LicenseError) { // WRITETRACK_LICENSE — missing, malformed, or unverifiable key } else if (e instanceof InputValidationError) { // WRITETRACK_INPUT — data shape is not a WriteTrackDataSchema } else if (e instanceof WasmLoadError) { // WRITETRACK_WASM — WASM failed to load; underlying error in `e.cause` } else if (e instanceof AnalysisError) { // WRITETRACK_ANALYSIS — analyzer output failed schema validation } else { throw e; }}All four error classes extend the abstract WriteTrackError base class and expose a stable code field (WRITETRACK_LICENSE, WRITETRACK_INPUT, WRITETRACK_WASM, WRITETRACK_ANALYSIS) for log aggregation. WasmLoadError forwards the loader exception as cause when one is available.
The default (errors: 'return') is preserved for backward compatibility; a future major version will flip the default to 'throw'.
formatIndicator()
Section titled “formatIndicator()”import { formatIndicator } from 'writetrack';
function formatIndicator(indicator: IndicatorOutput): string;Converts a machine-readable WT-NNN indicator code (from SessionAnalysis) into a human-readable English string.
const analysis = await tracker.getAnalysis();if (analysis) { const msg = formatIndicator(analysis.contentOrigin.indicator); // e.g., "85% of text was pasted from external sources"}createSessionReport()
Section titled “createSessionReport()”import { createSessionReport } from 'writetrack';
async function createSessionReport( rawData: unknown, options?: { wasmUrl?: string; license?: string }): Promise<SessionReport>;Produces a SessionReport ({ data, analysis }) ready for <wt-scorecard>.setData(). Accepts three input shapes:
WriteTrackDataSchema(fromgetData()) — analyzed viaanalyzeEvents().- Typing-sample format with
rawEventsinstead ofsession— normalized, then analyzed. - Wrapped
SessionReport({ data, analysis }) — returned as-is. Use this when analysis has already run on the server. No re-analysis, no license key needed. Ifanalysisis omitted, the innerdatais analyzed.
const data = await request.json();const report = await createSessionReport(data, { license: process.env.WRITETRACK_LICENSE_KEY,});// report.data — normalized input data// report.analysis — SessionAnalysis resultServer → client → scorecard
Section titled “Server → client → scorecard”When analyzeEvents() runs on your server, you typically persist or return { data, analysis } together. On the client, pass that wrapped object straight to createSessionReport() — analysis is trusted and preserved:
// Serverconst analysis = await analyzeEvents(data, { license: process.env.WRITETRACK_LICENSE_KEY,});return Response.json({ data, analysis });
// Clientconst wrapped = await fetch(`/sessions/${id}`).then((r) => r.json());const report = await createSessionReport(wrapped);document.querySelector('wt-scorecard').setData(report);WriteTrackController
Section titled “WriteTrackController”A framework-agnostic state management wrapper around WriteTrack. Used internally by the React and Vue hooks — exported for Svelte, Solid, vanilla JS, or any other framework.
import { WriteTrackController } from 'writetrack';import type { ControllerOptions, ControllerState } from 'writetrack';Constructor
Section titled “Constructor”const controller = new WriteTrackController(options: ControllerOptions);Methods
Section titled “Methods”| Method | Returns | Description |
|---|---|---|
start() | void | Start capturing events |
stop() | void | Stop capturing events |
destroy() | void | Stop and clean up all subscriptions |
reset() | void | Destroy, then re-initialize with the same options |
getState() | ControllerState | Get the current state snapshot |
ControllerOptions
Section titled “ControllerOptions”| Property | Type | Default | Description |
|---|---|---|---|
target | HTMLElement | — | The element to observe |
writeTrackOptions | Omit<WriteTrackOptions, 'target'> | — | Options forwarded to the WriteTrack constructor |
autoStart | boolean | true | Start capturing immediately after initialization |
analysis | boolean | false | Subscribe to WASM analysis updates |
onTick | (data: OnTickData) => void | undefined | Called on each tick (~1s) with timing data |
onReady | (tracker: WriteTrack) => void | undefined | Called when the tracker is ready |
onStateChange | (state: ControllerState) => void | — | Called whenever state changes (required) |
ControllerState
Section titled “ControllerState”| Property | Type | Description |
|---|---|---|
tracker | WriteTrack | null | The underlying tracker instance |
isReady | boolean | Whether the tracker is ready |
isTracking | boolean | Whether capture is active |
analysis | SessionAnalysis | null | Latest analysis result |
error | Error | null | Latest error (e.g., WASM load failure) |
WtScorecard
Section titled “WtScorecard”The <wt-scorecard> custom element renders a full session analysis report. See the Scorecard page for complete usage guide.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
theme | string | auto | "light" or "dark". Overrides system preference. |
share | boolean | false | Show share button and make QR clickable. |
download | boolean | false | Show download button. |
data | string | undefined | JSON-serialized SessionReport. |
compact | boolean | false | Compact rendering mode. |
label | string | undefined | Custom aria-label. |
Methods
Section titled “Methods”| Method | Signature | Description |
|---|---|---|
setData() | setData(data: SessionReport): void | Set data and trigger re-render |
getData() | getData(): SessionReport | null | Get current data |
register() | static register(): void | Register the custom element |
Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
wt-share | { report: SessionReport } | Share button/QR clicked |
wt-download | { report: SessionReport } | Download button clicked |
wt-render | none | Render complete |
WtBadge
Section titled “WtBadge”The <wt-badge> custom element renders a compact metric badge from a SessionAnalysis.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
data | string | undefined | JSON-serialized SessionAnalysis. |
theme | string | auto | "light" or "dark". Overrides system preference. |
compact | boolean | false | Compact rendering mode. |
metric | string | undefined | 'effort-ratio' | 'largest-paste' | 'corrections' | 'tab-aways'. |
Methods
Section titled “Methods”| Method | Signature | Description |
|---|---|---|
setData() | setData(data: SessionAnalysis): void | Set data and trigger re-render |
destroy() | destroy(): void | Clean up resources |
Events
Section titled “Events”| Event | Detail | Description |
|---|---|---|
wt-render | none | Render complete |
WtOriginBar
Section titled “WtOriginBar”The <wt-origin-bar> custom element renders a horizontal stacked bar showing content origin breakdown from a SessionAnalysis.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
data | string | undefined | JSON-serialized SessionAnalysis. |
theme | string | auto | "light" or "dark". Overrides system preference. |
compact | boolean | false | Compact rendering mode. |
Methods and events: same as WtBadge.
DocumentGrowth / ProcessGraph
Section titled “DocumentGrowth / ProcessGraph”The <wt-document-growth> custom element renders the writing-process graph — document length over time with paste markers and phase shading. Accepts WriteTrackDataSchema from getData(), not SessionAnalysis. ProcessGraph is exported as an alias of DocumentGrowth for callers that prefer the process-oriented name.
import { ProcessGraph, extractProcessData } from 'writetrack/viz';
ProcessGraph.register();document.querySelector('wt-document-growth').setData(tracker.getData());Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
data | string | undefined | JSON-serialized WriteTrackDataSchema. |
theme | string | auto | "light" or "dark". Overrides system preference. |
compact | boolean | false | Compact rendering mode. |
Methods and events: same as WtBadge.
DocumentRibbon
Section titled “DocumentRibbon”The <wt-document-ribbon> custom element renders a document composition timeline. Accepts WriteTrackDataSchema from getData(), not SessionAnalysis.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
data | string | undefined | JSON-serialized WriteTrackDataSchema. |
theme | string | auto | "light" or "dark". Overrides system preference. |
compact | boolean | false | Compact rendering mode. |
Methods and events: same as WtBadge.
IntegrityFooter
Section titled “IntegrityFooter”The <wt-integrity-footer> custom element renders a footer bar with hash verification, event count, and session ID.
Attributes
Section titled “Attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
data | string | undefined | JSON-serialized IntegrityFooterData. |
theme | string | auto | "light" or "dark". Overrides system preference. |
compact | boolean | false | Compact rendering mode. |
Methods and events: same as WtBadge.
getSessionDurationMs()
Section titled “getSessionDurationMs()”import { getSessionDurationMs } from 'writetrack';
function getSessionDurationMs(analysis: SessionAnalysis): number;Extract the session duration in milliseconds from a SessionAnalysis.
getTypingSpeed()
Section titled “getTypingSpeed()”import { getTypingSpeed } from 'writetrack';
function getTypingSpeed(analysis: SessionAnalysis): { cpm: number; wpm: number;};Compute characters-per-minute and words-per-minute from the keydown count and session duration in a SessionAnalysis. Returns { cpm: 0, wpm: 0 } when session duration is zero.
getContentOriginBreakdown()
Section titled “getContentOriginBreakdown()”import { getContentOriginBreakdown } from 'writetrack';
function getContentOriginBreakdown(analysis: SessionAnalysis): { typed: number; pasted: number; autocompleted: number;};Extract the content origin ratios (0-1 each) from a SessionAnalysis. These indicate what fraction of the final text was typed, pasted, or autocompleted.
const analysis = await tracker.getAnalysis();if (analysis) { const { wpm } = getTypingSpeed(analysis); const { typed, pasted } = getContentOriginBreakdown(analysis); console.log(`${wpm} WPM, ${Math.round(typed * 100)}% typed`);}Test Utilities
Section titled “Test Utilities”import { createMockAnalysis, createMockTracker } from 'writetrack/testing';Framework-agnostic factories for creating mock WriteTrack instances and SessionAnalysis objects in consumer tests. No vitest or jest dependency required.
createMockAnalysis()
Section titled “createMockAnalysis()”function createMockAnalysis( overrides?: DeepPartial<SessionAnalysis>): SessionAnalysis;Returns a complete SessionAnalysis with sensible defaults. Accepts deep-partial overrides that are merged into the defaults.
// Default analysis with realistic valuesconst analysis = createMockAnalysis();
// Override specific nested fieldsconst analysis = createMockAnalysis({ keydownCount: 500, contentOrigin: { metrics: { charactersByOrigin: { typed: 0.5, pasted: 0.5 } }, },});createMockTracker()
Section titled “createMockTracker()”function createMockTracker(options?: MockTrackerOptions): MockWriteTrack;Returns a mock object with the same public interface as WriteTrack. The mock supports on()/off()/removeAllListeners() event listeners, and exposes emit() for test-driven event firing.
const tracker = createMockTracker();
// With custom analysisconst tracker = createMockTracker({ analysis: createMockAnalysis({ keydownCount: 200 }),});
// With null analysis (simulates WASM load failure)const tracker = createMockTracker({ analysis: null });
// Test event flowtracker.on('tick', (payload) => console.log(payload));tracker.emit('tick', { activeTime: 5000, totalTime: 6000 });WriteTrackOptions
Section titled “WriteTrackOptions”Configuration options for the WriteTrack constructor.
interface WriteTrackOptions { target: HTMLElement; // Required: Input element to monitor license?: string; // Optional: License key for production userId?: string; // Optional: User identifier (included in metadata) contentId?: string; // Optional: Content/document identifier (included in metadata) metadata?: Record<string, unknown>; // Optional: Arbitrary tags (appears as metadata.custom) captureContext?: string; // Optional: Label for where in your app the tracker runs (appears as metadata.captureContext) wasmUrl?: string; // Optional: URL to WASM binary for analysis persist?: boolean; // Optional: Enable IndexedDB session persistence (requires contentId) cursorPositionProvider?: () => number; // Optional: Custom cursor position for rich-text editors inputSourceProvider?: () => InputSource | undefined; // Optional: Callback returning current input source classification. Set automatically by editor integrations.}PersistedSessionInfo
Section titled “PersistedSessionInfo”Metadata about a persisted writing session, returned by listPersistedSessions().
interface PersistedSessionInfo { contentId: string; // Document/content identifier fieldId: string; // Field identifier within the document keystrokeCount: number; // Number of keystrokes in the persisted session lastSavedAt: number; // Unix timestamp of last save sessionId: string; // Unique session ID}WriteTrackDataSchema
Section titled “WriteTrackDataSchema”The complete session output returned by getData().
interface WriteTrackDataSchema { version: '2.1.0'; metadata: SessionMetadata; session: RawEventData; quality: DataQualityMetrics; unlicensed?: boolean;}SessionMetadata
Section titled “SessionMetadata”Session metadata included in the export.
interface SessionMetadata { tool: { name: string; version: string }; targetElement: string; // e.g., 'textarea', 'input' fieldId: string; // Derived from data-writetrack-field, id, or name attribute sessionId: string; // Unique session ID (stable across resume) segment?: number; // Resume segment counter (present when session was resumed) timestamp: string; // ISO 8601 duration: number; // ms userId?: string; // From WriteTrackOptions.userId contentId?: string; // From WriteTrackOptions.contentId custom?: Record<string, unknown>; // From WriteTrackOptions.metadata}DataQualityMetrics
Section titled “DataQualityMetrics”Quality assessment of the captured session.
interface DataQualityMetrics { overallScore: number; // 0-1 completeness: number; // 0-1 sequenceValid: boolean; // Timestamps monotonically increasing timingValid: boolean; // Session has valid timing data sessionDuration: number; // ms eventCount: number; qualityLevel: 'POOR' | 'FAIR' | 'GOOD' | 'EXCELLENT'; issues: string[]; // Human-readable quality issues validatedAt: string; // ISO 8601}KeystrokeEvent
Section titled “KeystrokeEvent”Individual keystroke event data.
interface KeystrokeEvent { key: string; // Character or key name code: string; // Physical key code type: 'keydown' | 'keyup'; timestamp: number; // High-precision timestamp dwellTime?: number; // Key hold duration (keyup only) flightTime?: number; // Time since last key (keydown only) isCorrection?: boolean; // Backspace/Delete/ArrowLeft/ArrowRight windowFocused: boolean; timeSinceLastMouse: number; isTrusted?: boolean; // true if user-generated, false if scripted inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback isBurst?: boolean; // Schema field, populated by analysis pipelines cursorPosition?: number; // Schema field, populated by analysis pipelines modifiers?: ModifierState; // Schema field, populated by analysis pipelines}ClipboardEvent
Section titled “ClipboardEvent”Clipboard operation event data.
interface ClipboardEvent { type: 'copy' | 'paste' | 'cut'; timestamp: number; position: number; length: number; shortcut: string; // e.g., 'Ctrl+V', 'Cmd+V' content?: string; // Clipboard content (paste only) beforeText?: string; // Text state before operation afterText?: string; // Text state after operation replacedText?: string; // Text that was replaced replacedRange?: { start: number; end: number; }; isTrusted?: boolean; // true if user-generated, false if scripted inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback}SelectionEvent
Section titled “SelectionEvent”Text selection event data.
interface SelectionEvent { type: 'select'; timestamp: number; startPosition: number; endPosition: number; selectedLength: number; selectedText?: string; // The selected text content (omitted when privacy-sensitive) method: 'mouse' | 'keyboard' | 'programmatic'; isTrusted?: boolean; // true if user-generated, false if scripted}ProgrammaticInsertionEvent
Section titled “ProgrammaticInsertionEvent”Programmatic insertion event data.
interface ProgrammaticInsertionEvent { type: 'programmatic-insertion'; timestamp: number; insertedText: string; // Text that was inserted insertedLength: number; // Character count position: number; // Cursor position where text was inserted isTrusted: boolean; // Whether the triggering input event was user-generated inputType?: string; // InputEvent.inputType if available inputSource?: InputSource; // Input source classification, set by editor integrations or beforeinput fallback}UndoRedoEvent
Section titled “UndoRedoEvent”Undo/redo operation event data.
interface UndoRedoEvent { type: 'undo' | 'redo'; timestamp: number; shortcut: string; // e.g., 'Ctrl+Z' beforeText: string; afterText: string;}CompositionEvent
Section titled “CompositionEvent”IME composition event data. Emitted when a CJK input method or dead-key sequence completes.
interface CompositionEvent { timestamp: number; // When the composition started duration: number; // Duration of the composition sequence (ms) insertedLength: number; // Characters inserted (0 if composition was abandoned) isTrusted: boolean; // Whether the compositionend event was user-generated}SessionAnalysis
Section titled “SessionAnalysis”The WASM analysis output returned by getAnalysis(). Contains indicator codes and rich metrics for each analysis dimension.
interface SessionAnalysis { version: string; locale: string; analyzedAt: number; sufficientData: boolean; licensed: boolean; keydownCount: number; integrity: IntegrityProof; sessionTimeline: SessionSegment[]; initialTextLength: number; initialTextHash: string; finalTextLength: number; finalTextHash: string; contentOrigin: ContentOriginAnalysis; timingAuthenticity: TimingAuthenticityAnalysis; sessionContinuity: SessionContinuityAnalysis; physicalPlausibility: PhysicalPlausibilityAnalysis; revisionBehavior: RevisionBehaviorAnalysis; temporalPatterns: TemporalPatternsAnalysis; writingProcess: WritingProcessAnalysis; outputSignature: string; signedPayload?: string;}Each analysis dimension (e.g., contentOrigin, timingAuthenticity) contains an indicator with a machine-readable code (WT-100 through WT-606) and numeric params, plus a metrics object with detailed data and time series suitable for visualization. Content origin additionally has a pasteReworkIndicator (WT-105 through WT-107) for paste editing behavior.
The outputSignature and signedPayload fields are used internally for integrity checking.
WritingProcessAnalysis
Section titled “WritingProcessAnalysis”Writing process stage classification returned in SessionAnalysis.writingProcess.
interface WritingProcessAnalysis { indicator: IndicatorOutput; metrics: { segments: WritingProcessSegment[]; transitions: PhaseTransition[]; timeInPhase: { planning: number; // Fraction of session time (0-1) drafting: number; revision: number; }; phaseChangeCount: number; windowDurationMs: number; phaseTimeline: TimeSeries; };}
interface WritingProcessSegment { startTime: number; // ms from session start endTime: number; phase: 'planning' | 'drafting' | 'revision'; confidence: number; // 0-1 features: WindowFeatures;}
interface PhaseTransition { from: 'planning' | 'drafting' | 'revision'; to: 'planning' | 'drafting' | 'revision'; count: number; probability: number; // 0-1}SessionReport
Section titled “SessionReport”Combined output of getSessionReport().
interface SessionReport { data: WriteTrackDataSchema; analysis: SessionAnalysis | null;}IndicatorOutput
Section titled “IndicatorOutput”Machine-readable indicator returned by each analysis dimension.
interface IndicatorOutput { code: string; // WT-NNN code (e.g., "WT-101") params: Record<string, number>; // Numeric parameters (e.g., { pct: 85 })}Use formatIndicator() to convert to a human-readable English string.