Skip to content

Commit f99997f

Browse files
fix: stamp userId on traits_updated session events (#48)
* fix: stamp userId on traits_updated events Slim session payloads omitted userId from traits_updated even though the sink already had the runtime id. Collector could not associate a traits bag with the current user unless setUserId also ran in the debounce window. Co-authored-by: Deepak Gupta <deepakg202@users.noreply.github.com> * chore: bump packages to 0.8.12 CI requires a tugboat version bump. Keep tugboat_dio on the same lockstep line; adapter behavior is unchanged. Co-authored-by: Deepak Gupta <deepakg202@users.noreply.github.com> * fix: stamp userId and traits on all session events user_changed and session_end now send the current runtime userId plus the traits bag (or cached traitsId). Slim payloads had dropped those fields, so the collector lost identity on user-only updates and on session close. Co-authored-by: Deepak Gupta <deepakg202@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Deepak Gupta <deepakg202@users.noreply.github.com>
1 parent dfc8161 commit f99997f

15 files changed

Lines changed: 126 additions & 60 deletions

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ should be verified in their own repositories.
3030

3131
## Current compatibility
3232

33-
- package version: `0.8.11`;
34-
- release line: `0.8.x` (`0.8.11` is a patch after `0.8.10`);
33+
- package version: `0.8.12`;
34+
- release line: `0.8.x` (`0.8.12` is a patch after `0.8.11`);
3535
- session JSON schema: `10`;
3636
- fingerprint schema: `6`;
3737
- minimum Dart SDK: `3.9.2`;

docs/integration/collector.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,15 @@ not uploaded before this handshake completes.
197197

198198
Session payloads may include:
199199

200+
- `userId` — current runtime user on every session lifecycle event, including
201+
`session_end`. After start, `null` is an explicit identity-clear on
202+
`user_changed` / `session_identify` and the current (possibly anonymous)
203+
identity on `traits_updated` and `session_end`;
200204
- `traits` — full traits snapshot when the host has set a bag (`session_start`,
201-
`session_identify`, `traits_updated`, `user_changed`); the collector stores
202-
the bag as-is (no server-side partial merge);
205+
`session_identify`, `traits_updated`, `user_changed`, `session_end`); the
206+
collector stores the bag as-is (no server-side partial merge);
203207
- `traitsId` — pass-through of a prior collector-issued id when no new bag is
204-
sent (for example `session_end`, or `session_start` after only an id is
208+
sent (for example `session_end` or `session_start` after only an id is
205209
cached). Ignored by the collector when `traits` is present.
206210

207211
Accepted session responses (`202`) may return `traitsId`. The SDK caches that

docs/integration/production-replay-acceptance.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ database receipt alone as proof that a replay is correct.
1212

1313
## Current acceptance status
1414

15-
The current SDK release candidate is **0.8.11**, which writes session schema
15+
The current SDK release candidate is **0.8.12**, which writes session schema
1616
**v10**. It preserves structural interaction replay while no longer emitting
1717
`controlValue`, `controlValueTransition`, `semanticAnnotation`, `stateAnchor`,
1818
or `stateSignature` in new writer output. It also does not emit `state_change`.
@@ -177,7 +177,7 @@ flows share a session, list the event IDs or timestamps that delimit each flow.
177177

178178
Wait until the collector session has finalized and the replay is available in
179179
the production website. Filter to the recorded Blend build and SDK version
180-
under test (`0.8.11` for this release), then open every recorded session.
180+
under test (`0.8.12` for this release), then open every recorded session.
181181

182182
For each interaction, inspect the actual replay UI and verify:
183183

packages/tugboat/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## 0.8.12
2+
3+
Patch release after `0.8.11`.
4+
5+
### Fixed
6+
7+
- Stamp the current runtime `userId` and traits snapshot on every session
8+
lifecycle POST (`session_start`, `session_identify`, `traits_updated`,
9+
`user_changed`, `session_end`). Slim payloads had omitted identity from
10+
traits-only updates and omitted both fields from `session_end`.
11+
112
## 0.8.11
213

314
Patch release after `0.8.10`.

packages/tugboat/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ 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.8.11`. Session JSON writers and readers use
8+
The current package version is `0.8.12`. Session JSON writers and readers use
99
schema version `10` only. Structural fingerprints use fingerprint schema
1010
version `6`.
1111

12+
## 0.8.12
13+
14+
Session lifecycle POSTs now stamp the current runtime `userId` and traits on
15+
`session_start`, `session_identify`, `traits_updated`, `user_changed`, and
16+
`session_end`.
17+
1218
## 0.8.11
1319

1420
The SDK now enforces a maximum cyclomatic complexity of 10. Internal helpers
@@ -99,7 +105,7 @@ The package requires Dart 3.9.2 or newer and Flutter 3.35.0 or newer.
99105

100106
```yaml
101107
dependencies:
102-
tugboat_dio: ^0.8.11
108+
tugboat_dio: ^0.8.12
103109
```
104110
105111
See `packages/tugboat_dio/README.md`.

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.8.11
35+
tugboat: ^0.8.12
3636

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

packages/tugboat/lib/src/collector_http_sink.dart

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -399,16 +399,16 @@ class CollectorHttpSink implements TugboatCaptureSink {
399399
if (session == null) return _SendResult.accepted;
400400

401401
final sessionId = _sessionIdForLifecycle(eventType, session.id);
402-
final includeFullTraits = _includesFullTraits(eventType);
402+
final traits = _traits;
403403

404404
final body = mapTugboatSessionLifecycleToCollectorSession(
405405
eventType: eventType,
406406
sessionId: sessionId,
407407
triggeredAt: triggeredAt,
408408
config: _config,
409409
userId: _userId,
410-
traits: includeFullTraits ? _traits : null,
411-
traitsId: includeFullTraits ? null : _traitsId,
410+
traits: traits,
411+
traitsId: traits == null ? _traitsId : null,
412412
activeLocale: activeLocale,
413413
);
414414

@@ -434,14 +434,6 @@ class CollectorHttpSink implements TugboatCaptureSink {
434434
? localSessionId
435435
: _collectorSessionId ?? localSessionId;
436436

437-
bool _includesFullTraits(String eventType) =>
438-
_traits != null &&
439-
(_isSessionStart(eventType) ||
440-
eventType ==
441-
TugboatCollectorSessionEventType.sessionIdentify.wireValue ||
442-
eventType ==
443-
TugboatCollectorSessionEventType.traitsUpdated.wireValue);
444-
445437
bool _isSessionStart(String eventType) =>
446438
eventType == TugboatCollectorSessionEventType.sessionStart.wireValue;
447439

packages/tugboat/lib/src/collector_mapper.dart

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,16 @@ Map<String, Object?> mapTugboatSessionLifecycleToCollectorSession({
158158
TugboatLocaleInfo? activeLocale,
159159
}) {
160160
final isSessionStart = _isSessionStart(eventType);
161-
final carriesUserId = _carriesUserId(eventType);
162-
final carriesTraits = _carriesTraits(eventType);
163161

164162
final body = <String, Object?>{
165163
'sessionId': sessionId,
166164
'eventType': eventType,
167165
'triggeredAt': triggeredAt.toUtc().toIso8601String(),
166+
// Only session_start inherits the configured startup identity. Later
167+
// lifecycle records send the runtime id as-is, including null.
168+
'userId': isSessionStart ? userId ?? config.userId : userId,
168169
};
169170

170-
if (carriesUserId) {
171-
// Only session_start inherits the configured startup identity. Later
172-
// lifecycle records use null as an explicit identity-clear operation.
173-
body['userId'] = isSessionStart ? userId ?? config.userId : userId;
174-
}
175171
if (isSessionStart) {
176172
final appInfo = Map<String, Object?>.from(config.appInfo.toJson())
177173
..remove('installationId')
@@ -192,27 +188,15 @@ Map<String, Object?> mapTugboatSessionLifecycleToCollectorSession({
192188
'locale': locale,
193189
});
194190
}
195-
if (carriesTraits) {
196-
// Full traits bag wins over traitsId pass-through.
197-
if (traits != null) body['traits'] = traits;
198-
if (traits == null && traitsId != null) body['traitsId'] = traitsId;
199-
}
191+
// Full traits bag wins over traitsId pass-through.
192+
if (traits != null) body['traits'] = traits;
193+
if (traits == null && traitsId != null) body['traitsId'] = traitsId;
200194
return body;
201195
}
202196

203197
bool _isSessionStart(String eventType) =>
204198
eventType == TugboatCollectorSessionEventType.sessionStart.wireValue;
205199

206-
bool _carriesUserId(String eventType) =>
207-
_isSessionStart(eventType) ||
208-
eventType == TugboatCollectorSessionEventType.sessionIdentify.wireValue ||
209-
eventType == TugboatCollectorSessionEventType.userChanged.wireValue;
210-
211-
bool _carriesTraits(String eventType) =>
212-
_isSessionStart(eventType) ||
213-
eventType == TugboatCollectorSessionEventType.sessionIdentify.wireValue ||
214-
eventType == TugboatCollectorSessionEventType.traitsUpdated.wireValue;
215-
216200
/// Trailing digits from a tugboat frame id (`frame-12``12`).
217201
/// Returns null when the id does not end in digits.
218202
int? frameNumberFromId(String frameId) {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// Keep this in sync with packages/tugboat/pubspec.yaml. The SDK version test
22
// reads pubspec.yaml directly so release bumps fail fast if this drifts.
3-
const tugboatSdkVersion = '0.8.11';
3+
const tugboatSdkVersion = '0.8.12';

packages/tugboat/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: tugboat
22
description: >-
33
Screenshot-based session replay with compact interaction anchors for Tugboat.
4-
version: 0.8.11
4+
version: 0.8.12
55
repository: https://github.com/blendto/tugboat-flutter
66
issue_tracker: https://github.com/blendto/tugboat-flutter/issues
77
homepage: https://github.com/blendto/tugboat-flutter

0 commit comments

Comments
 (0)