Skip to content

Commit 603bed3

Browse files
committed
chore(tugboat): bump package version to 0.5.3 and update documentation
- Updated package version in `pubspec.yaml`, `README.md`, and `CHANGELOG.md` to reflect the new version 0.5.3. - Adjusted compatibility information in documentation to align with the new version. - Enhanced documentation to clarify the behavior of identity management during session lifecycles.
1 parent a7097a2 commit 603bed3

6 files changed

Lines changed: 51 additions & 27 deletions

File tree

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.1`;
31+
- package version: `0.5.3`;
3232
- session JSON schema: `9`;
3333
- fingerprint schema: `6`;
3434
- minimum Dart SDK: `3.9.2`;

packages/tugboat/README.md

Lines changed: 1 addition & 1 deletion
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.1`. 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

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.1
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.

packages/tugboat/lib/src/collector_http_sink.dart

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@ class CollectorHttpSink implements TugboatCaptureSink {
1919
Map<String, dynamic>? initialTraits,
2020
String? initialTraitsId,
2121
String? initialUserId,
22+
@visibleForTesting Duration? identityDebounceDuration,
2223
}) : _config = config,
2324
_userId = initialUserId ?? config.userId,
2425
_traits = initialTraits == null
2526
? null
2627
: Map<String, dynamic>.from(initialTraits),
2728
_traitsId = initialTraitsId,
29+
_identityDebounceDuration =
30+
identityDebounceDuration ?? _defaultIdentityDebounceDuration,
2831
_client = _CollectorHttpClient(
2932
inner: client ?? http.Client(),
3033
apiKey: config.apiKey,
@@ -66,7 +69,9 @@ class CollectorHttpSink implements TugboatCaptureSink {
6669
/// billing, feature flags). Debouncing merges those into one lifecycle POST
6770
/// (`session_identify` when both change) instead of back-to-back
6871
/// `user_changed` / `traits_updated`.
69-
static const _identityDebounceDuration = Duration(seconds: 3);
72+
static const _defaultIdentityDebounceDuration = Duration(seconds: 3);
73+
74+
final Duration _identityDebounceDuration;
7075

7176
Timer? _identityDebounceTimer;
7277
bool _userDirty = false;
@@ -199,10 +204,11 @@ class CollectorHttpSink implements TugboatCaptureSink {
199204

200205
/// Registers a full traits snapshot with the collector.
201206
///
202-
/// No-ops when [traits] deep-equals the cached bag. While `session_start` is
203-
/// still pending, updates memory only (folded into start at send time).
204-
/// Otherwise debounces `traits_updated` or `session_identify` when combined
205-
/// with a pending user change within [_identityDebounceDuration].
207+
/// No-ops when [traits] equals the cached bag via [mapEquals] (shallow:
208+
/// nested maps/lists compared with `==`). While `session_start` is still
209+
/// pending, updates memory only (folded into start at send time). Otherwise
210+
/// debounces `traits_updated` or `session_identify` when combined with a
211+
/// pending user change within [_identityDebounceDuration].
206212
Future<void> setTraits(Map<String, dynamic> traits) async {
207213
if (_disposed) return;
208214
if (mapEquals(_traits, traits)) return;

packages/tugboat/lib/src/tugboat.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ class TugboatReplay {
104104

105105
/// Updates the runtime user id used on collector sessions and events.
106106
///
107-
/// No-ops when [userId] equals the current runtime id. When a capture session
108-
/// is active and the id changes, debounces lifecycle posts (3s). Combined
109-
/// with a pending traits change, posts `session_identify`; otherwise
107+
/// Always records a remount override via [hasPendingUserIdOverride]. Collector
108+
/// posting no-ops when [userId] equals the current runtime id. When a capture
109+
/// session is active and the id changes, debounces lifecycle posts (3s).
110+
/// Combined with a pending traits change, posts `session_identify`; otherwise
110111
/// `user_changed`. While `session_start` is still pending, updates memory only.
111112
static Future<void> setUserId(String? userId) async {
112113
_pendingUserId = userId;

packages/tugboat/test/collector_http_sink_test.dart

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,27 @@ void main() {
205205
expect(headers['X-Sdk-Version'], tugboatSdkVersion);
206206
}
207207

208+
/// Short debounce so identity tests avoid ~3s wall-clock sleeps.
209+
const testIdentityDebounce = Duration(milliseconds: 40);
210+
211+
CollectorHttpSink createIdentitySink({
212+
Map<String, dynamic>? initialTraits,
213+
String? initialTraitsId,
214+
String? initialUserId,
215+
}) {
216+
return CollectorHttpSink(
217+
config: configForServer(),
218+
initialTraits: initialTraits,
219+
initialTraitsId: initialTraitsId,
220+
initialUserId: initialUserId,
221+
identityDebounceDuration: testIdentityDebounce,
222+
);
223+
}
224+
208225
Future<void> awaitIdentityDebounce() async {
209-
await Future<void>.delayed(const Duration(milliseconds: 3100));
226+
await Future<void>.delayed(
227+
testIdentityDebounce + const Duration(milliseconds: 40),
228+
);
210229
}
211230

212231
test('collector defaults flush low-volume events every 3 seconds', () {
@@ -796,7 +815,7 @@ void main() {
796815
'setTraits posts traits_updated, caches traitsId, stamps next events',
797816
() async {
798817
sessionResponseTraitsId = 'trt_abc';
799-
final sink = CollectorHttpSink(config: configForServer());
818+
final sink = createIdentitySink();
800819
final session = createSession();
801820
sink.startSession(session);
802821
await Future<void>.delayed(const Duration(milliseconds: 50));
@@ -898,7 +917,7 @@ void main() {
898917
'new session_start includes traits bag after mid-session setTraits',
899918
() async {
900919
sessionResponseTraitsId = 'trt_mid';
901-
final sink = CollectorHttpSink(config: configForServer());
920+
final sink = createIdentitySink();
902921
sink.startSession(createSession(id: 'session-a'));
903922
await Future<void>.delayed(const Duration(milliseconds: 50));
904923
expect(sessionPosts, hasLength(1));
@@ -944,8 +963,7 @@ void main() {
944963

945964
test('setUserId posts user_changed with cached traits', () async {
946965
sessionResponseTraitsId = 'trt_user';
947-
final sink = CollectorHttpSink(
948-
config: configForServer(),
966+
final sink = createIdentitySink(
949967
initialTraits: {'plan': 'pro'},
950968
initialUserId: 'user_a',
951969
);
@@ -1018,10 +1036,7 @@ void main() {
10181036
);
10191037

10201038
test('setUserId no-ops when user id is unchanged', () async {
1021-
final sink = CollectorHttpSink(
1022-
config: configForServer(),
1023-
initialUserId: 'user_a',
1024-
);
1039+
final sink = createIdentitySink(initialUserId: 'user_a');
10251040
sink.startSession(createSession());
10261041
await Future<void>.delayed(const Duration(milliseconds: 50));
10271042
expect(sessionPosts, hasLength(1));
@@ -1045,7 +1060,7 @@ void main() {
10451060

10461061
test('setTraits no-ops when traits bag is unchanged', () async {
10471062
sessionResponseTraitsId = 'trt_skip';
1048-
final sink = CollectorHttpSink(config: configForServer());
1063+
final sink = createIdentitySink();
10491064
sink.startSession(createSession());
10501065
await Future<void>.delayed(const Duration(milliseconds: 50));
10511066
expect(sessionPosts, hasLength(1));
@@ -1071,7 +1086,7 @@ void main() {
10711086
'setUserId then setTraits within debounce posts session_identify once',
10721087
() async {
10731088
sessionResponseTraitsId = 'trt_both';
1074-
final sink = CollectorHttpSink(config: configForServer());
1089+
final sink = createIdentitySink();
10751090
sink.startSession(createSession());
10761091
await Future<void>.delayed(const Duration(milliseconds: 50));
10771092
expect(sessionPosts, hasLength(1));
@@ -1097,7 +1112,7 @@ void main() {
10971112
);
10981113

10991114
test('debounced identity uses first dirty time as triggeredAt', () async {
1100-
final sink = CollectorHttpSink(config: configForServer());
1115+
final sink = createIdentitySink();
11011116
sink.startSession(createSession());
11021117
await Future<void>.delayed(const Duration(milliseconds: 50));
11031118

@@ -1121,7 +1136,9 @@ void main() {
11211136
isTrue,
11221137
);
11231138
expect(
1124-
triggeredAt.isBefore(afterDebounce.subtract(const Duration(seconds: 2))),
1139+
triggeredAt.isBefore(
1140+
afterDebounce.subtract(const Duration(milliseconds: 20)),
1141+
),
11251142
isTrue,
11261143
);
11271144
sink.dispose();
@@ -1130,12 +1147,12 @@ void main() {
11301147
test(
11311148
'coalesced identity triggeredAt reflects latest dirty mark when both change',
11321149
() async {
1133-
final sink = CollectorHttpSink(config: configForServer());
1150+
final sink = createIdentitySink();
11341151
sink.startSession(createSession());
11351152
await Future<void>.delayed(const Duration(milliseconds: 50));
11361153

11371154
await sink.setUserId('user_at');
1138-
await Future<void>.delayed(const Duration(milliseconds: 200));
1155+
await Future<void>.delayed(const Duration(milliseconds: 15));
11391156
final beforeTraits = DateTime.now();
11401157
await sink.setTraits({'plan': 'pro'});
11411158
final afterTraits = DateTime.now();
@@ -1161,7 +1178,7 @@ void main() {
11611178

11621179
test('endSession flushes debounced identity before session_end', () async {
11631180
sessionResponseTraitsId = 'trt_end';
1164-
final sink = CollectorHttpSink(config: configForServer());
1181+
final sink = createIdentitySink();
11651182
sink.startSession(createSession());
11661183
await Future<void>.delayed(const Duration(milliseconds: 50));
11671184

0 commit comments

Comments
 (0)