Skip to content

Commit 7dff6f0

Browse files
committed
feat(replay): remove control value tracking
1 parent 20df6e9 commit 7dff6f0

24 files changed

Lines changed: 262 additions & 2913 deletions

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ verified in their own repositories.
2828

2929
## Current compatibility
3030

31-
- package version: `0.4.17`;
32-
- session JSON schema: `8`;
31+
- package version: `0.5.0`;
32+
- session JSON schema: `9`;
3333
- fingerprint schema: `6`;
3434
- minimum Dart SDK: `3.9.2`;
3535
- minimum Flutter SDK: `3.35.0`.

docs/design/capture-and-fingerprint.md

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ current controller and keeps future calls to `wrapApp` inert. Runtime
6363
requiring a host rebuild. `deactivate()` tears capture down through the same
6464
gate. Pause/hidden flush pending delivery; detach ends the session once.
6565

66-
Identity fields (session schema **v7**; readers accept v6):
66+
Identity fields (session schema **v9**; compatibility readers accept v6–v9):
6767

6868
- `activationRequestId` — host request correlation
6969
- `captureSessionId` — SDK-emitted session (`session.id`)
@@ -76,7 +76,10 @@ emits exact build and fingerprint-schema provenance only.
7676
## Session and event model
7777

7878
The controller owns one bounded, in-memory `TugboatSession`. Serialized session
79-
JSON is schema version `7`. Readers accept schema versions `6` and `7`.
79+
JSON is schema version `9`. Compatibility readers accept schema versions
80+
`6` through `9`. Schema v9 does not write `controlValue`,
81+
`controlValueTransition`, or `semanticAnnotation` in event `data`;
82+
those fields are optional historic data in older sessions only.
8083

8184
The session stores:
8285

@@ -235,43 +238,9 @@ Developer-authored identity strings can still be emitted:
235238
- widget type names or configured `widgetNames` replacements;
236239
- canonical structural paths.
237240

238-
Interaction events may also carry a `controlValue` payload (schema version 4) for
239-
valued controls (checkbox, switch, radio, slider, dropdown / menu item, chip)
240-
and for hit targets that expose Flutter semantic annotations:
241-
242-
- bools and numbers are emitted literally;
243-
- enums and developer identifiers are emitted literally;
244-
- arbitrary strings, including numeric strings and single-word values, are
245-
emitted literally;
246-
- explicit custom-control values can be supplied with
247-
`TugboatControlValueScope`, including a stable `controlKey`, optional unit,
248-
and numeric `min`, `max`, and `step` metadata.
249-
250-
`tap` includes a `controlValue` snapshot sampled at pointer-down.
251-
`tap_settled` uses the distinct `controlValueTransition` contract with
252-
`before` / `after` snapshots. Its post-callback sample stays bound to the
253-
original hit element, so later taps, route changes, or dismissed overlays
254-
cannot donate unrelated control state. Slider drags that become `swipe` events
255-
carry a `controlValue` snapshot sampled at pointer-up.
256-
257-
When a typed widget value is unavailable (custom GestureDetector rows, bottom
258-
sheets, etc.), the SDK still samples `SemanticsProperties` / live semantics
259-
nodes under the pointer and records raw `semanticValue` / `semanticLabel`.
260-
Standard controls may include both widget
261-
state and semantic annotations under `sources: ["semantics","widget"]`.
262-
263-
Independently, every interaction event (`tap`, `tap_settled`, `swipe`,
264-
`scroll_start`, `scroll_end`) may carry a top-level `semanticAnnotation`
265-
payload (schema version 2) whenever Flutter semantics expose an identifier, label, value, or
266-
selection flag on the target. This covers ordinary buttons and scrollables as
267-
well as valued controls. The field is named `semanticAnnotation` to avoid
268-
colliding with `tap_settled.data.settleObservation.semantic` (state-signature
269-
change evidence).
270-
271241
Bounds, pointer coordinates, scroll metrics, and masked screenshot pixels are
272-
also capture data. Apps must treat tags, route names, subview labels, and
273-
semantic value/label tokens as telemetry and avoid putting raw user PII in
274-
them.
242+
also capture data. Apps must treat tags, route names, and subview labels as
243+
telemetry and avoid putting user data in them.
275244

276245
## Screenshot pipeline
277246

packages/tugboat/CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
## 0.5.0
2+
3+
### Breaking changes
4+
5+
- **Control-value and semantic-annotation telemetry** — session JSON writers
6+
now emit schema version 9 and no longer write `controlValue`,
7+
`controlValueTransition`, or `semanticAnnotation` in event `data`.
8+
Readers that support historic schemas should continue to tolerate versions
9+
6–8, where those fields may be present.
10+
- **Removed public barrel exports**
11+
`TugboatEncodedControlScalar`, `TugboatVisibleControlValue`,
12+
`TugboatControlValueScope`, `TugboatControlValue`, and
13+
`TugboatSemanticAnnotation`.
14+
- **Removed public schema constants**
15+
`tugboatControlValueSchemaVersion`,
16+
`tugboatControlValueTransitionSchemaVersion`, and
17+
`tugboatSemanticAnnotationSchemaVersion`.
18+
- **Removed public extraction and merge helpers**
19+
`tugboatControlValueForWidget`,
20+
`tugboatControlValueFromSemanticsProperties`,
21+
`tugboatControlValueFromSemanticsNode`,
22+
`tugboatSemanticAnnotationFromProperties`,
23+
`tugboatSemanticAnnotationFromNode`,
24+
`tugboatMergeSemanticAnnotations`, and `tugboatMergeControlValues`.
25+
126
## 0.4.18
227

328
### Fixed

packages/tugboat/README.md

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ 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.4.18`. Session JSON uses schema version `8`
9-
(readers still accept `6`), and structural fingerprints use fingerprint schema
10-
version `6`.
8+
The current package version is `0.5.0`. Session JSON writers emit schema
9+
version `9`; compatibility readers should accept versions `6` through
10+
`9`. Structural fingerprints use fingerprint schema version `6`.
1111

1212
## Install
1313

@@ -19,6 +19,29 @@ import 'package:tugboat/tugboat.dart';
1919

2020
The package requires Dart 3.9.2 or newer and Flutter 3.35.0 or newer.
2121

22+
## Migrating to 0.5.0
23+
24+
This is a breaking release. Session JSON written by 0.5.0 uses schema version
25+
`9` and no longer includes `controlValue`, `controlValueTransition`, or
26+
`semanticAnnotation` in event `data`. Consumers reading historical schemas
27+
`6``8` should treat those fields as optional historic data; new captures do
28+
not provide them.
29+
30+
The public `package:tugboat/tugboat.dart` barrel no longer exports:
31+
32+
- `TugboatEncodedControlScalar`, `TugboatVisibleControlValue`,
33+
`TugboatControlValueScope`, `TugboatControlValue`, and
34+
`TugboatSemanticAnnotation`;
35+
- `tugboatControlValueSchemaVersion`,
36+
`tugboatControlValueTransitionSchemaVersion`, and
37+
`tugboatSemanticAnnotationSchemaVersion`;
38+
- `tugboatControlValueForWidget`,
39+
`tugboatControlValueFromSemanticsProperties`,
40+
`tugboatControlValueFromSemanticsNode`,
41+
`tugboatSemanticAnnotationFromProperties`,
42+
`tugboatSemanticAnnotationFromNode`,
43+
`tugboatMergeSemanticAnnotations`, and `tugboatMergeControlValues`.
44+
2245
## Minimal integration
2346

2447
Install both the app wrapper and navigator observer. Capture is dormant by
@@ -209,11 +232,10 @@ Available mask levels are `explicitOnly`, `allTextAndMedia`, `allText`,
209232
stay visible; other custom-painted or decorated image surfaces are not
210233
classified by this mode, so wrap them in `TugboatSensitive` when needed).
211234

212-
Control values and semantic strings are retained verbatim so session summaries
213-
and aggregate analysis can use values such as slider positions, video duration,
214-
and selected templates. Dynamic list discriminators remain hashed before they
215-
enter canonical paths. Telemetry also includes developer-authored routing and
216-
identity strings where applicable:
235+
The structural telemetry does not retain arbitrary `Text`, accessibility,
236+
tooltip, or icon label strings. Dynamic list discriminators are hashed before
237+
they enter canonical paths. Telemetry does include developer-authored routing
238+
and identity strings where applicable:
217239

218240
- route names in `route_change.data` and anchor `routeKey` fields;
219241
- `TugboatSubView.label` in state/scroll context;
@@ -223,35 +245,10 @@ identity strings where applicable:
223245
- normalized bounds, pointer coordinates, scroll metrics, and screenshot
224246
pixels after the configured masking policy is applied.
225247

226-
Screenshots and telemetry can contain rendered or semantic user content. Choose
227-
an explicit production masking policy and test custom widgets, platform views,
228-
overlays, and semantic labels before enabling production capture.
229-
230-
### Explicit custom-control values
231-
232-
Standard Flutter controls expose their typed state automatically. Wrap custom
233-
controls when the app knows a more useful stable key, unit, or range:
234-
235-
```dart
236-
TugboatControlValueScope(
237-
controlKey: 'video_duration',
238-
role: 'slider',
239-
unit: 'milliseconds',
240-
min: 1_000,
241-
max: 60_000,
242-
step: 1_000,
243-
value: TugboatVisibleControlValue.duration(
244-
const Duration(seconds: 15),
245-
),
246-
child: MyDurationSlider(),
247-
)
248-
```
249-
250-
For template or preset selection, use a stable enum identifier:
251-
252-
```dart
253-
value: TugboatVisibleControlValue.enumId('modern_minimal'),
254-
```
248+
Screenshots are the only captured surface that can contain rendered user
249+
content. Choose an explicit production masking policy and test custom widgets,
250+
platform views, and overlays in the target app before enabling production
251+
capture.
255252

256253
## Event and frame model
257254

packages/tugboat/example/lib/screens/profile_screen.dart

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
1616
bool _darkMode = false;
1717
double _notificationVolume = 0.6;
1818
String _language = 'English';
19-
int _generationCount = 1;
2019

2120
@override
2221
Widget build(BuildContext context) {
@@ -144,28 +143,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
144143
),
145144
],
146145
),
147-
DemoSection(
148-
title: 'Generation settings',
149-
children: [
150-
Wrap(
151-
spacing: 8,
152-
children: [
153-
for (final count in [1, 2, 3, 4])
154-
Semantics(
155-
label: 'Number of generations',
156-
value: count.toString(),
157-
selected: _generationCount == count,
158-
child: FilledButton(
159-
key: Key('generation-count-$count'),
160-
onPressed: () =>
161-
setState(() => _generationCount = count),
162-
child: Text(count.toString()),
163-
),
164-
),
165-
],
166-
),
167-
],
168-
),
169146
DemoSection(
170147
title: 'Account actions',
171148
children: [

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.4.0
35+
tugboat: ^0.5.0
3636

3737
# The following adds the Cupertino Icons font to your application.
3838
# Use with the CupertinoIcons class for iOS style icons.
Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,7 @@
1-
import 'package:flutter/material.dart';
2-
import 'package:flutter_test/flutter_test.dart';
3-
import 'package:tugboat/tugboat.dart';
41
import 'package:tugboat_example/main.dart';
5-
6-
Future<void> _waitForTugboatEvents(WidgetTester tester) async {
7-
for (var attempt = 0; attempt < 12; attempt++) {
8-
await tester.pump(const Duration(milliseconds: 50));
9-
}
10-
}
11-
12-
Map<String, Object?> _semanticAnnotation(TugboatEvent event) {
13-
final raw = event.data['semanticAnnotation'];
14-
return Map<String, Object?>.from(raw! as Map);
15-
}
2+
import 'package:flutter_test/flutter_test.dart';
163

174
void main() {
18-
setUp(TugboatReplay.resetForTest);
19-
tearDown(TugboatReplay.resetForTest);
20-
215
testWidgets('demo app loads home screen', (tester) async {
226
await tester.pumpWidget(const ReplayDemoApp());
237
await tester.pump();
@@ -28,37 +12,4 @@ void main() {
2812
await tester.pump(const Duration(milliseconds: 300));
2913
}
3014
});
31-
32-
testWidgets(
33-
'generation count tap emits its semantic parameter label and value',
34-
(tester) async {
35-
await tester.binding.setSurfaceSize(const Size(800, 1200));
36-
addTearDown(() => tester.binding.setSurfaceSize(null));
37-
TugboatReplay.activate(
38-
activationRequestId: 'example-semantic-parameter-test',
39-
profile: TugboatCaptureProfile.productionLean,
40-
);
41-
await tester.pumpWidget(const ReplayDemoApp());
42-
await _waitForTugboatEvents(tester);
43-
44-
await tester.tap(find.text('Profile & settings'));
45-
await tester.pumpAndSettle();
46-
await _waitForTugboatEvents(tester);
47-
48-
await tester.tap(find.byKey(const Key('generation-count-3')));
49-
await _waitForTugboatEvents(tester);
50-
51-
final tap = TugboatReplay.controller!.session!.events.lastWhere(
52-
(event) => event.type == 'tap',
53-
);
54-
final annotation = _semanticAnnotation(tap);
55-
56-
expect(annotation['label'], {
57-
'kind': 'string',
58-
'value': 'Number of generations',
59-
});
60-
expect(annotation['value'], {'kind': 'string', 'value': '3'});
61-
expect(annotation['selected'], isFalse);
62-
},
63-
);
6415
}

0 commit comments

Comments
 (0)