Skip to content

Commit 3253dc0

Browse files
authored
Merge pull request #33 from blendto/user-traits
User traits
2 parents b16332a + 603bed3 commit 3253dc0

14 files changed

Lines changed: 1197 additions & 53 deletions

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ verified in their own repositories.
2828

2929
## Current compatibility
3030

31-
- package version: `0.5.0`;
31+
- package version: `0.5.3`;
3232
- session JSON schema: `9`;
3333
- fingerprint schema: `6`;
3434
- minimum Dart SDK: `3.9.2`;

docs/integration/collector.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ The SDK calls:
159159

160160
| Request | Purpose |
161161
| --- | --- |
162-
| `POST /v1/sessions` | `session_start` and `session_end` lifecycle payloads |
162+
| `POST /v1/sessions` | Session lifecycle and identity: `session_start`, `session_identify`, `session_end`, `traits_updated`, `user_changed` |
163163
| `POST /v1/events/batch` | JSON event batches |
164164
| `POST /v1/frames` | multipart PNG frame upload |
165165

@@ -173,10 +173,30 @@ an accepted start response and reads its `sessionId`; events, frames, and the
173173
end lifecycle request then use that collector-issued ID. Events and frames are
174174
not uploaded before this handshake completes.
175175

176+
Session payloads may include:
177+
178+
- `traits` — full traits snapshot when the host has set a bag (`session_start`,
179+
`session_identify`, `traits_updated`, `user_changed`); the collector stores
180+
the bag as-is (no server-side partial merge);
181+
- `traitsId` — pass-through of a prior collector-issued id when no new bag is
182+
sent (for example `session_end`, or `session_start` after only an id is
183+
cached). Ignored by the collector when `traits` is present.
184+
185+
Accepted session responses (`202`) may return `traitsId`. The SDK caches that
186+
value in process memory and stamps it onto subsequent event batches. Host apps
187+
register traits with `TugboatReplay.setTraits` and change the runtime user with
188+
`TugboatReplay.setUserId`. While `session_start` is still pending, both APIs
189+
update in-memory identity only (folded into start at send time). After start,
190+
changes within 3s coalesce into one lifecycle POST: `session_identify` when
191+
both user and traits change, otherwise `user_changed` or `traits_updated`.
192+
Debounced updates are flushed before `session_end`. The SDK does **not** call
193+
`/v1/identify` or `/v1/events/identify`.
194+
176195
Event payloads contain:
177196

178197
- event ID, type, `atMs`, and absolute UTC `triggeredAt`;
179198
- optional user/session/run/action IDs;
199+
- optional `traitsId` (pass-through only; does not upsert the traits dictionary);
180200
- optional before/after frame references, related-event ID, and result;
181201
- serialized state and target anchors;
182202
- event-specific data under `payload`;

packages/tugboat/CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,46 @@
1+
## 0.5.3
2+
3+
### Added
4+
5+
- **Debounced identity coalesce** — after `session_start`, `setUserId` and
6+
`setTraits` within 3s consolidate into one `session_identify` POST when
7+
both change; otherwise `user_changed` or `traits_updated`. Pending updates are
8+
flushed before `session_end`.
9+
10+
### Changed
11+
12+
- Pre-start identity still folds into a single `session_start` when values are
13+
staged before or while start is pending.
14+
15+
## 0.5.2
16+
17+
### Changed
18+
19+
- **`setUserId` folds into pending `session_start`** — when a `session_start`
20+
is still pending, `CollectorHttpSink.setUserId` updates the runtime id only
21+
and skips `user_changed` (same coalesce already used by `setTraits`). Boot
22+
identity can land on a single `session_start` POST.
23+
24+
## 0.5.1
25+
26+
### Added
27+
28+
- **User traits via collector sessions**`TugboatReplay.setTraits` posts
29+
`eventType: traits_updated` on `POST /v1/sessions` with a full traits bag,
30+
caches the response `traitsId`, and stamps it on event batches.
31+
`TugboatReplay.setUserId` posts `user_changed` and updates the runtime user
32+
id. Pre-set traits are included on the next `session_start`. No
33+
`/v1/identify` route.
34+
35+
### Changed
36+
37+
- **`setUserId` skips unchanged ids** — calling `TugboatReplay.setUserId` /
38+
`CollectorHttpSink.setUserId` with the same value as the current runtime
39+
user id does not post `user_changed`.
40+
- **Pre-initialize identify**`setTraits` / `setUserId` called after the
41+
controller mounts but before the HTTP sink is created retain identity for
42+
the next `session_start` instead of dropping it.
43+
144
## 0.5.0
245

346
### Breaking changes

packages/tugboat/README.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ checkpoints around meaningful interactions, compact structural anchors, route
55
transitions, scrolling evidence, and optional viewport semantic maps. Capture
66
can be sent to the local exploration WebSocket, the HTTP collector, or both.
77

8-
The current package version is `0.5.0`. Session JSON writers emit schema
8+
The current package version is `0.5.3`. Session JSON writers emit schema
99
version `9`; compatibility readers should accept versions `6` through
1010
`9`. Structural fingerprints use fingerprint schema version `6`.
1111

@@ -153,11 +153,37 @@ Identity contract:
153153
- `captureSessionId` (`session.id`) — SDK-generated emitted evidence session
154154
- `collectorSessionId` — stamped after HTTP `session_start` acceptance
155155
- `explorationRunId` — exploration control-plane ID from config
156+
- `traitsId` — collector-issued traits dictionary id after `setTraits` / session responses
156157

157158
`TugboatReplay.activeSessionId` remains as a deprecated alias for
158159
`activationRequestId`. Inspect `TugboatReplay.health` for sink/outbox/screenshot
159160
budget pressure without reading protected content.
160161

162+
### User traits and user id
163+
164+
When an HTTP collector is configured, register a full traits snapshot (not a
165+
partial merge) via `POST /v1/sessions`:
166+
167+
```dart
168+
await TugboatReplay.setTraits({
169+
'plan': 'pro',
170+
'seatCount': 3,
171+
});
172+
173+
await TugboatReplay.setUserId(currentUserId);
174+
```
175+
176+
- `setTraits` debounces `traits_updated` (3s) after start acceptance, or
177+
`session_identify` when combined with a pending user change. Caches
178+
`traitsId` and stamps it on event batches. While `session_start` is pending,
179+
updates memory only (folded into start at send time).
180+
- `setUserId` debounces `user_changed` (3s) after start acceptance, or
181+
`session_identify` when combined with a pending traits change. Unchanged ids
182+
are ignored. While `session_start` is pending, updates memory only.
183+
- Pre-activate calls are retained in memory and included on the next
184+
`session_start` when present. Pending debounced updates flush on `session_end`.
185+
There is no `/v1/identify` route.
186+
161187
Optional durable HTTP delivery (Collector only, default off):
162188

163189
```dart
@@ -263,7 +289,7 @@ Emitted event types currently include:
263289
immutable `origin`, `result`, `attribution`, and `evidenceEventIds`;
264290
- legacy gesture peers (`stream: legacy_projection` when canonical is on):
265291
`tap`, `tap_settled`, `swipe`, `tap_outside_tree`, `tap_gesture_resolved`;
266-
- lifecycle: `session_start`, `session_end`;
292+
- lifecycle: `session_start`, `session_identify`, `session_end`;
267293
- input: `pointer_cancel` (`stream: evidence`);
268294
- state/navigation evidence (`stream: evidence`): `state_change`, `route_change`
269295
(claimed routes also carry `causedByInteractionId`);
@@ -380,11 +406,12 @@ so it is suitable for health polling and cannot grow with session duration.
380406

381407
## Public surface
382408

383-
The supported import exports `TugboatReplay`, `TugboatNavigatorObserver`,
384-
`TugboatReplayConfig`, capture/semantic/masking enums and policies, collector
385-
configuration and host helpers, markers (`TugboatSensitive`, `TugboatTag`,
386-
`TugboatSubView`, `TugboatInternal`), anchor and session models, the controller,
387-
and `TugboatExplorationTransport`.
409+
The supported import exports `TugboatReplay` (including `setTraits` /
410+
`setUserId`), `TugboatNavigatorObserver`, `TugboatReplayConfig`,
411+
capture/semantic/masking enums and policies, collector configuration and host
412+
helpers, markers (`TugboatSensitive`, `TugboatTag`, `TugboatSubView`,
413+
`TugboatInternal`), anchor and session models, the controller, and
414+
`TugboatExplorationTransport`.
388415

389416
`TugboatCaptureSink` and the built-in sink implementations are internal today;
390417
config supports only the WebSocket and HTTP destinations above. A stable custom

packages/tugboat/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ resolution: workspace
3232
dependencies:
3333
flutter:
3434
sdk: flutter
35-
tugboat: ^0.5.0
35+
tugboat: ^0.5.3
3636

3737
# The following adds the Cupertino Icons font to your application.
3838
# Use with the CupertinoIcons class for iOS style icons.

0 commit comments

Comments
 (0)