writetrack 0.10.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # WriteTrack
2
2
 
3
- Detect AI-generated and pasted text through keystroke dynamics.
3
+ Capture and analyze the writing process through keystroke dynamics.
4
4
 
5
- WriteTrack analyzes _how_ text was entered, not what was written. Human typing has natural variation in timing, rhythm, and corrections. Pasted or AI-generated text arrives all at once with no behavioral fingerprint. WriteTrack captures the difference.
5
+ WriteTrack is an embeddable SDK that instruments text inputs to record _how_ text was written timing, rhythm, corrections, clipboard usage, and composition patterns. It gives platforms behavioral evidence of the writing process, not just the finished text.
6
6
 
7
- - **~118KB** gzipped (18KB JS + 100KB WASM)
7
+ - **~142KB** gzipped (20KB JS + 122KB WASM)
8
8
  - **0** runtime dependencies
9
9
  - **<1ms** per keystroke
10
10
  - **100%** client-side
@@ -15,12 +15,14 @@ WriteTrack analyzes _how_ text was entered, not what was written. Human typing h
15
15
  npm install writetrack
16
16
  ```
17
17
 
18
- Then register for a free trial:
18
+ Optionally, register for a free trial license:
19
19
 
20
20
  ```bash
21
- npx writetrack init
21
+ npx writetrack init # starts a 28-day trial and writes the key to .env
22
22
  ```
23
23
 
24
+ A license is required for production domains. On `localhost`, all features work without a license key.
25
+
24
26
  ## Quick Start
25
27
 
26
28
  ```typescript
@@ -29,7 +31,6 @@ import { WriteTrack } from 'writetrack';
29
31
  const responseField = document.getElementById('response-field')!;
30
32
  const tracker = new WriteTrack({
31
33
  target: responseField,
32
- license: process.env.WRITETRACK_LICENSE_KEY, // omit for localhost evaluation
33
34
  });
34
35
 
35
36
  tracker.start();
@@ -45,6 +46,51 @@ tracker.stop();
45
46
 
46
47
  Works with any text input, textarea, or contenteditable element. TypeScript definitions included.
47
48
 
49
+ > **Note:** WriteTrack uses WASM, which requires an HTTP server — `file://` won't work. Any bundler (Vite, webpack, etc.) handles this automatically. For a no-bundler setup, see the [vanilla JS guide](https://writetrack.dev/docs/vanilla-js/).
50
+
51
+ ### React / Next.js
52
+
53
+ ```tsx
54
+ import { useRef } from 'react';
55
+ import { useWriteTrack } from 'writetrack/react';
56
+
57
+ function ResponseForm() {
58
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
59
+ const { tracker } = useWriteTrack(textareaRef);
60
+
61
+ const handleSubmit = async (e: React.FormEvent) => {
62
+ e.preventDefault();
63
+ if (tracker) {
64
+ const data = tracker.getData();
65
+ const analysis = await tracker.getAnalysis();
66
+ console.log('Session:', data.quality, analysis);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <form onSubmit={handleSubmit}>
72
+ <textarea ref={textareaRef} rows={10} />
73
+ <button type="submit">Submit</button>
74
+ </form>
75
+ );
76
+ }
77
+ ```
78
+
79
+ For App Router, Server Actions, and dev-mode WASM setup, see the [Next.js guide](https://writetrack.dev/docs/nextjs/).
80
+
81
+ ## Production
82
+
83
+ Add your license key to enable analysis on production domains:
84
+
85
+ ```typescript
86
+ const tracker = new WriteTrack({
87
+ target: textarea,
88
+ license: process.env.WRITETRACK_LICENSE_KEY,
89
+ });
90
+ ```
91
+
92
+ On `localhost`, all features work without a key. See the [licensing guide](https://writetrack.dev/docs/license/) for trial setup, domain binding, and expiration details.
93
+
48
94
  ## Framework Integrations
49
95
 
50
96
  First-party bindings available as subpath exports:
@@ -58,7 +104,7 @@ import { WriteTrackPlugin } from 'writetrack/prosemirror';
58
104
  import { WriteTrackModule } from 'writetrack/quill';
59
105
  import { createWriteTrackLexical } from 'writetrack/lexical';
60
106
  import { createWriteTrackSlate } from 'writetrack/slate';
61
- import { createWriteTrackTinyMCE } from 'writetrack/tinymce';
107
+ import 'writetrack/tinymce'; // registers native TinyMCE plugin
62
108
  ```
63
109
 
64
110
  ## Output Sinks
@@ -77,7 +123,7 @@ tracker
77
123
 
78
124
  ## What It Captures
79
125
 
80
- WriteTrack instruments your text inputs and records the behavioral signals behind every keystroke:
126
+ WriteTrack instruments text inputs and records the behavioral signals behind every keystroke:
81
127
 
82
128
  - **Timing intervals** — milliseconds between each keystroke
83
129
  - **Rhythm variance** — consistency or erratic cadence
@@ -86,13 +132,29 @@ WriteTrack instruments your text inputs and records the behavioral signals behin
86
132
  - **Selection events** — text selections via mouse, keyboard, or programmatic
87
133
  - **Composition events** — IME input for CJK and other non-Latin scripts
88
134
 
135
+ ## What It Produces
136
+
137
+ WASM-powered analysis returns a `SessionAnalysis` covering seven dimensions:
138
+
139
+ - **Content origin** — what proportion was typed, pasted, or autocompleted
140
+ - **Timing authenticity** — rhythm consistency, periodicity, entropy
141
+ - **Revision behavior** — corrections, navigation, undo/redo patterns
142
+ - **Session continuity** — focus changes, tab-aways, session structure
143
+ - **Physical plausibility** — impossible sequences, synthetic event markers
144
+ - **Temporal patterns** — speed over time, warmup/fatigue, burst patterns
145
+ - **Writing process** — planning, drafting, and revision phase segmentation
146
+
147
+ Each dimension produces a machine-readable indicator code and detailed metrics suitable for visualization.
148
+
89
149
  ## Use Cases
90
150
 
91
- **Education** — Flag essays that were pasted rather than composed. Give instructors behavioral context alongside content.
151
+ **Education** — Give instructors behavioral context alongside submitted work. See whether text was composed or pasted.
152
+
153
+ **Assessment** — Add a behavioral layer to written responses. Distinguish engaged composition from copy-paste submission.
92
154
 
93
- **Research** — Distinguish survey respondents who engaged thoughtfully from those who copy-pasted boilerplate.
155
+ **Compliance** — Know when form attestations were typed versus pasted.
94
156
 
95
- **Compliance** — Add a behavioral layer to form submissions. Know when attestations were typed versus pasted.
157
+ **Research** — Capture writing process data for studies on composition behavior, revision patterns, and typing fluency.
96
158
 
97
159
  ## API
98
160
 
@@ -120,29 +182,54 @@ interface WriteTrackOptions {
120
182
 
121
183
  ### Methods
122
184
 
123
- | Method | Returns | Description |
124
- | ---------------------------------- | -------------------------------- | ------------------------------------------------- |
125
- | `start()` | `void` | Begin capturing events |
126
- | `stop()` | `void` | Stop capturing and clean up |
127
- | `getData()` | `WriteTrackDataSchema` | Export full session data |
128
- | `getText()` | `string` | Current text content |
129
- | `getRawEvents()` | `KeystrokeEvent[]` | All captured keystroke events |
130
- | `getClipboardEvents()` | `ClipboardEvent[]` | Paste/copy/cut events |
131
- | `getSelectionEvents()` | `SelectionEvent[]` | Text selection events |
132
- | `getUndoRedoEvents()` | `UndoRedoEvent[]` | Undo/redo events |
133
- | `getCompositionEvents()` | `CompositionEvent[]` | IME composition events |
134
- | `getProgrammaticInsertionEvents()` | `ProgrammaticInsertionEvent[]` | Programmatic insertion events |
135
- | `getSessionDuration()` | `number` | Session duration in ms |
136
- | `getActiveTime()` | `number` | Active (visible) session time in ms |
137
- | `getKeystrokeCount()` | `number` | Total keystrokes captured |
138
- | `getAnalysis()` | `Promise<SessionAnalysis\|null>` | WASM-powered session analysis |
139
- | `getSessionReport()` | `Promise<SessionReport>` | Combined data + analysis |
140
- | `pipe(sink)` | `this` | Register an output sink |
141
- | `on(event, handler)` | `this` | Register an event listener (`tick`, `pipe:error`) |
142
- | `isLicenseValid()` | `boolean` | Whether license is valid |
143
- | `isLicenseValidated()` | `boolean` | Whether license check has completed |
144
- | `isTargetDetached()` | `boolean` | Whether the target element was removed |
145
- | `clearPersistedData()` | `Promise<void>` | Clear IndexedDB persisted session |
185
+ | Method | Returns | Description |
186
+ | ---------------------------------- | -------------------------------- | --------------------------------------------- |
187
+ | `start()` | `void` | Begin capturing events |
188
+ | `stop()` | `void` | Stop capturing and clean up |
189
+ | `stopAndWait()` | `Promise<void>` | Stop and wait for pending IndexedDB save |
190
+ | `getData()` | `WriteTrackDataSchema` | Export full session data |
191
+ | `getText()` | `string` | Current text content |
192
+ | `getRawEvents()` | `KeystrokeEvent[]` | All captured keystroke events |
193
+ | `getClipboardEvents()` | `ClipboardEvent[]` | Paste/copy/cut events |
194
+ | `getSelectionEvents()` | `SelectionEvent[]` | Text selection events |
195
+ | `getUndoRedoEvents()` | `UndoRedoEvent[]` | Undo/redo events |
196
+ | `getCompositionEvents()` | `CompositionEvent[]` | IME composition events |
197
+ | `getProgrammaticInsertionEvents()` | `ProgrammaticInsertionEvent[]` | Programmatic insertion events |
198
+ | `getSessionDuration()` | `number` | Session duration in ms |
199
+ | `getActiveTime()` | `number` | Active (visible) session time in ms |
200
+ | `getKeystrokeCount()` | `number` | Total keystrokes captured |
201
+ | `getAnalysis()` | `Promise<SessionAnalysis\|null>` | WASM-powered session analysis |
202
+ | `getSessionReport()` | `Promise<SessionReport>` | Combined data + analysis |
203
+ | `pipe(sink)` | `this` | Register an output sink |
204
+ | `on(event, handler)` | `() => void` | Register event listener (returns unsubscribe) |
205
+ | `isLicenseValid()` | `boolean` | Whether license is valid |
206
+ | `isLicenseValidated()` | `boolean` | Whether license check has completed |
207
+ | `isTargetDetached()` | `boolean` | Whether the target element was removed |
208
+ | `clearPersistedData()` | `Promise<void>` | Clear IndexedDB persisted session |
209
+
210
+ `getData()` returns a `WriteTrackDataSchema` with four top-level fields: `version`, `metadata` (session context, user/content IDs, custom fields), `session` (raw events and text), and `quality` (completeness score, validation status). See the [API reference](https://writetrack.dev/docs/api-reference#getdata) for the full shape.
211
+
212
+ ### Events
213
+
214
+ The `on(event, handler)` method supports the following events:
215
+
216
+ | Event | Payload | Description |
217
+ | ------------ | ---------------------------------------------------------------- | --------------------------------------------------------------- |
218
+ | `change` | `{ eventCount: number; keystrokeCount: number }` | Fires on any data mutation (keystroke, paste, selection, etc.) |
219
+ | `tick` | `{ activeTime: number; totalTime: number; tracker: WriteTrack }` | Fires every ~1s while session is active |
220
+ | `analysis` | `SessionAnalysis` | Fires when analysis updates (lazy-loads WASM on first listener) |
221
+ | `ready` | _(none)_ | Fires when tracker is ready (late listeners auto-fire) |
222
+ | `wasm:ready` | _(none)_ | Fires when WASM binary loads successfully |
223
+ | `wasm:error` | `Error` | Fires if WASM binary fails to load |
224
+ | `stop` | _(none)_ | Fires when `stop()` is called |
225
+ | `pipe:error` | `Error, WriteTrackSink` | Fires when an output sink fails |
226
+
227
+ ```typescript
228
+ const unsub = tracker.on('change', ({ eventCount, keystrokeCount }) => {
229
+ console.log(`${keystrokeCount} keystrokes, ${eventCount} total events`);
230
+ });
231
+ // Later: unsub();
232
+ ```
146
233
 
147
234
  ### Static Methods
148
235
 
@@ -171,24 +258,58 @@ const analysis = await analyzeEvents(data, {
171
258
  });
172
259
  ```
173
260
 
261
+ ### Analysis Helpers
262
+
263
+ Convenience functions for extracting commonly needed values from a `SessionAnalysis`:
264
+
265
+ | Function | Returns | Description |
266
+ | ------------------------------------- | ---------------------------------------------------------- | -------------------------------- |
267
+ | `getSessionDurationMs(analysis)` | `number` | Session duration in milliseconds |
268
+ | `getTypingSpeed(analysis)` | `{ cpm: number; wpm: number }` | Characters and words per minute |
269
+ | `getContentOriginBreakdown(analysis)` | `{ typed: number; pasted: number; autocompleted: number }` | Content origin ratios (0-1 each) |
270
+
271
+ ```typescript
272
+ import { getTypingSpeed, getContentOriginBreakdown } from 'writetrack';
273
+
274
+ const analysis = await tracker.getAnalysis();
275
+ if (analysis) {
276
+ const { wpm } = getTypingSpeed(analysis);
277
+ const { typed, pasted } = getContentOriginBreakdown(analysis);
278
+ }
279
+ ```
280
+
281
+ ### Testing Utilities
282
+
283
+ The `writetrack/testing` subpath export provides framework-agnostic factories for creating mock WriteTrack instances and `SessionAnalysis` objects in consumer tests:
284
+
285
+ ```typescript
286
+ import { createMockAnalysis, createMockTracker } from 'writetrack/testing';
287
+
288
+ const analysis = createMockAnalysis({ keydownCount: 500 });
289
+ const tracker = createMockTracker({ analysis });
290
+ ```
291
+
292
+ | Function | Returns | Description |
293
+ | -------------------------------- | ----------------- | -------------------------------------------------- |
294
+ | `createMockAnalysis(overrides?)` | `SessionAnalysis` | Complete mock analysis with deep-partial overrides |
295
+ | `createMockTracker(options?)` | `MockWriteTrack` | Mock tracker with configurable analysis and data |
296
+
174
297
  ### Exported Types
175
298
 
176
299
  Full type definitions are included in the package:
177
300
 
178
- **Functions:** `analyzeEvents`, `createSessionReport`, `formatIndicator`, `getHighResolutionTime`
301
+ **Functions:** `analyzeEvents`, `createSessionReport`, `formatIndicator`, `getHighResolutionTime`, `WriteTrackController`
179
302
 
180
- **Types:** `WriteTrackOptions`, `PersistedSessionInfo`, `KeystrokeEvent`, `ClipboardEvent`, `SelectionEvent`, `UndoRedoEvent`, `CompositionEvent`, `ProgrammaticInsertionEvent`, `ModifierState`, `InputSource`, `WriteTrackDataSchema`, `DataQualityMetrics`, `SessionMetadata`, `SessionAnalysis`, `SessionReport`, `IndicatorOutput`, `ContentOriginAnalysis`, `TimingAuthenticityAnalysis`, `SessionContinuityAnalysis`, `PhysicalPlausibilityAnalysis`, `RevisionBehaviorAnalysis`, `TemporalPatternsAnalysis`, `AnalyzeEventsOptions`, `WriteTrackSink`, `WebhookOptions`, `DatadogOptions`, `DatadogClient`, `SegmentOptions`, `SegmentClient`, `OpenTelemetryOptions`, `OTelTracer`, `OTelSpan`
303
+ **Types:** `WriteTrackOptions`, `PersistedSessionInfo`, `KeystrokeEvent`, `ClipboardEvent`, `SelectionEvent`, `UndoRedoEvent`, `CompositionEvent`, `ProgrammaticInsertionEvent`, `ModifierState`, `InputSource`, `WriteTrackDataSchema`, `DataQualityMetrics`, `SessionMetadata`, `SessionAnalysis`, `SessionReport`, `IndicatorOutput`, `ContentOriginAnalysis`, `TimingAuthenticityAnalysis`, `SessionContinuityAnalysis`, `PhysicalPlausibilityAnalysis`, `RevisionBehaviorAnalysis`, `TemporalPatternsAnalysis`, `WritingProcessAnalysis`, `WritingProcessSegment`, `PhaseTransition`, `WindowFeatures`, `AnalyzeEventsOptions`, `WriteTrackSink`, `WebhookOptions`, `DatadogOptions`, `DatadogClient`, `SegmentOptions`, `SegmentClient`, `OpenTelemetryOptions`, `OTelTracer`, `OTelSpan`, `BaseBindingOptions`, `OnTickData`, `ControllerState`, `ControllerOptions`
181
304
 
182
305
  ## Browser Support
183
306
 
184
307
  | Browser | Version |
185
308
  | ------- | ------- |
186
- | Chrome | 62+ |
187
- | Firefox | 56+ |
188
- | Safari | 14.1+ |
189
- | Edge | 79+ |
190
-
191
- All features including license verification work at these versions. License validation uses ECDSA P-256 via `crypto.subtle`, which has been supported since Chrome 37+, Firefox 34+, Safari 11+, and Edge 79+.
309
+ | Chrome | 90+ |
310
+ | Firefox | 88+ |
311
+ | Safari | 14+ |
312
+ | Edge | 90+ |
192
313
 
193
314
  ## Privacy
194
315