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 +163 -42
- package/dist/browser/index.js +1 -1
- package/dist/browser/pipes.js +1 -1
- package/dist/browser/viz.js +56 -42
- package/dist/browser/writetrack.wasm +0 -0
- package/dist/ckeditor/index.d.ts +2 -18
- package/dist/ckeditor/index.js +1 -1
- package/dist/esm/index.d.ts +141 -6
- package/dist/esm/index.js +1 -1
- package/dist/esm/pipes.d.ts +3 -10
- package/dist/esm/pipes.js +1 -1
- package/dist/esm/testing.d.ts +61 -0
- package/dist/esm/testing.js +1 -0
- package/dist/esm/verify.d.ts +1 -1
- package/dist/esm/viz.d.ts +157 -37
- package/dist/esm/viz.js +56 -42
- package/dist/esm/writetrack.wasm +0 -0
- package/dist/index.cjs +1 -1
- package/dist/lexical/index.d.ts +2 -18
- package/dist/lexical/index.js +1 -1
- package/dist/pipes.cjs +1 -1
- package/dist/prosemirror/index.d.ts +2 -16
- package/dist/prosemirror/index.js +1 -1
- package/dist/quill/index.d.ts +2 -16
- package/dist/quill/index.js +1 -1
- package/dist/react/index.d.ts +16 -17
- package/dist/react/index.js +1 -1
- package/dist/slate/index.d.ts +2 -16
- package/dist/slate/index.js +1 -1
- package/dist/tinymce/index.d.ts +28 -58
- package/dist/tinymce/index.js +1 -1
- package/dist/tiptap/index.d.ts +15 -18
- package/dist/tiptap/index.js +1 -1
- package/dist/viz.cjs +57 -43
- package/dist/vue/index.d.ts +11 -16
- package/dist/vue/index.js +1 -1
- package/dist/writetrack.wasm +0 -0
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# WriteTrack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Capture and analyze the writing process through keystroke dynamics.
|
|
4
4
|
|
|
5
|
-
WriteTrack
|
|
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
|
-
- **~
|
|
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
|
-
|
|
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
|
|
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
|
|
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** —
|
|
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
|
-
**
|
|
155
|
+
**Compliance** — Know when form attestations were typed versus pasted.
|
|
94
156
|
|
|
95
|
-
**
|
|
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
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
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 |
|
|
187
|
-
| Firefox |
|
|
188
|
-
| Safari | 14
|
|
189
|
-
| Edge |
|
|
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
|
|