Remove legacy interaction formats and state identity - #36
Conversation
There was a problem hiding this comment.
Pull request overview
Slims lifecycle payloads while expanding the 0.8.0 wire-format and replay capture behavior.
Changes:
- Sends static context only at session start and delta identity data afterward.
- Removes serialized state identity and adds fresh interaction-owned frames.
- Updates tests, documentation, and package versions.
Reviewed changes
Copilot reviewed 37 out of 38 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
.gitignore |
Ignores VS Code settings. |
docs/README.md |
Updates current version. |
docs/design/capture-and-fingerprint.md |
Documents new event and capture semantics. |
docs/integration/collector.md |
Updates collector and suppression behavior. |
docs/integration/production-replay-acceptance.md |
Revises 0.8.0 acceptance criteria. |
packages/tugboat/CHANGELOG.md |
Adds 0.8.0 release notes. |
packages/tugboat/README.md |
Documents compatibility and capture changes. |
packages/tugboat/pubspec.yaml |
Bumps Tugboat to 0.8.0. |
packages/tugboat/example/pubspec.yaml |
Updates example dependency. |
packages/tugboat/lib/src/anchor_models.dart |
Omits state signatures from semantic JSON. |
packages/tugboat/lib/src/capture_boundary.dart |
Reformats paint hashing. |
packages/tugboat/lib/src/collector_http_sink.dart |
Limits lifecycle traits transmission. |
packages/tugboat/lib/src/collector_mapper.dart |
Produces slim lifecycle payloads. |
packages/tugboat/lib/src/controller.dart |
Reworks interaction, route, and scroll captures. |
packages/tugboat/lib/src/debug_logging.dart |
Removes state signatures from logs. |
packages/tugboat/lib/src/interaction_transaction.dart |
Tracks route epochs and capture outcomes. |
packages/tugboat/lib/src/models.dart |
Adds interaction frame trigger and omits state anchors. |
packages/tugboat/lib/src/screenshot_capturer.dart |
Exposes encoder injection and reformats capture code. |
packages/tugboat/lib/src/screenshot_encode_isolate.dart |
Reformats isolate replies. |
packages/tugboat/lib/src/sdk_version.dart |
Updates SDK version constant. |
packages/tugboat/lib/src/viewport_semantic_session.dart |
Revises semantic deduplication and scroll accumulation. |
packages/tugboat/test/collector_http_sink_test.dart |
Updates lifecycle payload expectations. |
packages/tugboat/test/collector_mapper_test.dart |
Tests slim lifecycle mapping. |
packages/tugboat/test/replay/interaction_transaction_test.dart |
Tests route-state cleanup. |
packages/tugboat/test/replay/modal_capture_visual_test.dart |
Stabilizes modal capture timing. |
packages/tugboat/test/replay/replay_navigation_interaction_matrix_test.dart |
Updates state-anchor expectations. |
packages/tugboat/test/replay/replay_navigation_race_matrix_test.dart |
Tests automatic-route attribution. |
packages/tugboat/test/replay/replay_overlay_nested_navigation_matrix_test.dart |
Awaits settled navigation evidence. |
packages/tugboat/test/replay/screenshot_encode_isolate_test.dart |
Reformats isolate test. |
packages/tugboat/test/replay/screenshot_fresh_paint_test.dart |
Reformats fresh-paint test. |
packages/tugboat/test/replay_coherence_characterization_test.dart |
Expands interaction capture coverage. |
packages/tugboat/test/scene_inventory_test.dart |
Updates state-signature assertions. |
packages/tugboat/test/scroll_attribution_test.dart |
Tests scroll callback ordering and programmatic scrolling. |
packages/tugboat/test/tugboat_replay_test.dart |
Tests fresh interaction frames and state omission. |
packages/tugboat/test/viewport_semantic_map_test.dart |
Tests semantic rebuilding and gesture resets. |
packages/tugboat_dio/CHANGELOG.md |
Adds compatibility release notes. |
packages/tugboat_dio/README.md |
Updates dependency documentation. |
packages/tugboat_dio/pubspec.yaml |
Bumps adapter and Tugboat dependency. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| }; | ||
|
|
||
| if (carriesUserId) { | ||
| body['userId'] = userId ?? config.userId; |
| final resolution = await capture.resolution; | ||
| if (!_isCaptureLifecycleCurrent(session, lifecycleEpoch)) return; |
| if (scrollContext?.trigger == 'scroll_start') { | ||
| _beginScrollSemanticGesture(map); | ||
| } |
| @@ -242,7 +252,6 @@ class TugboatEvent { | |||
| if (sessionId != null) 'sessionId': sessionId, | |||
| if (captureSessionId != null) 'captureSessionId': captureSessionId, | |||
| if (activationRequestId != null) 'activationRequestId': activationRequestId, | |||
| static String _routeCaptureKey(String? navigatorId) => navigatorId ?? ''; | ||
|
|
||
| final Map<Element, _ScrollTracker> _scrollTrackers = {}; | ||
| final Map<String, InteractionTransaction> _scrollInteractions = {}; |
| route, | ||
| lifecycle, | ||
| manual, | ||
| interaction, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated no new comments.
Suppressed comments (3)
packages/tugboat/lib/src/controller.dart:3488
- Completed pointer-linked scrolls are held only in
_scrollInteractions, not_activeCompletedGestureCaptures. Clearing this map on session replacement/end or lifecycle deactivation therefore drops the canonical scroll interaction entirely while its capture is pending, contrary to the completed-gesture preservation requirement. Finalize these transactions with the appropriate cancellation reason before clearing the scroll state.
void _clearScrollCompletionState() {
_scrollTrackers.clear();
_scrollInteractions.clear();
_pendingScrollCompletions.clear();
packages/tugboat/lib/src/viewport_semantic_session.dart:366
- If the current inventory cannot build a semantic map (for example, semantics are temporarily unavailable or the payload exceeds the limit),
maybeEmitleaves the previous_latestMapintact and the tap is resolved against stale controls. Clear the cached map before attempting this current-inventory rebuild so failure resolves as unavailable instead of matching the prior screen.
if (inventory != null) {
maybeEmit(inventory, resolver: resolver);
}
docs/README.md:32
- The release metadata now declares session wire schema 10, but this compatibility summary still advertises schema 9. Update it so integrators do not configure readers for the obsolete writer version.
- session JSON schema: `9`;
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff0c387aaf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| _scrollInteractions.clear(); | ||
| _pendingScrollCompletions.clear(); |
There was a problem hiding this comment.
Finalize queued scroll interactions before clearing them
When pointer-up has classified a gesture as a scroll but its scroll-end capture has not resolved, the completed transaction exists only in _scrollInteractions. Ending or replacing the session, or entering an inactive lifecycle state, calls this method after cancelling capture work, so clearing these maps silently drops the transaction without publishing the terminal canonical interaction; unlike completed swipes, these scrolls are not included in _activeCompletedGestureCaptures. Finalize the queued scroll transactions with a cancelled capture outcome before discarding this state.
Useful? React with 👍 / 👎.
Remove state-signature plumbing and reshape interaction, route_change, and scroll collector events as flat facts-only records so downstream consumers derive outcomes from evidence instead of inferred SDK fields. Co-authored-by: Cursor <cursoragent@cursor.com>
Fold tap, swipe, scroll, and cancel facts into interaction schema v2 with a nested gesture payload, and stop emitting scroll_start, scroll_end, and pointer_cancel as separate production events. Co-authored-by: Cursor <cursoragent@cursor.com>
Update core docs to describe nested interaction payload gestures and the removal of scroll_start, scroll_end, and pointer_cancel from production wire. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 62 changed files in this pull request and generated 3 comments.
Suppressed comments (2)
packages/tugboat/lib/src/viewport_semantic_session.dart:160
- This event-level dedupe returns before
_recordScrollSemanticSlice. Because everyscroll_startnow replaces the gesture accumulator, repeating the same semantic slices in a later gesture leaves the new accumulator empty and suppresses itsscroll_semantic_snapshot. Record each scroll slice before applying dedupe only toviewport_semantic_mappublication.
final dedupeKey =
'${map.routeKey}|${map.mapHash}|${map.scrollContext?.dedupeKey ?? ''}';
if (!_emittedSemanticMaps.add(dedupeKey)) return;
packages/tugboat/test/replay/replay_overlay_nested_navigation_matrix_test.dart:123
- This helper now searches the entire session and returns the first matching route. The test closes
/roottwice (dialog, then sheet), so the second lookup can return the earlier dialog-pop event and pass even if the sheet pop emitted nothing. Restore an event-index baseline/cursor and search only events produced after the current action.
| @@ -3,7 +3,6 @@ library; | |||
| export 'src/anchors.dart' | |||
| show | |||
| TugboatNormalizedBounds, | |||
| static TugboatSession fromJson(Map<String, dynamic> json) { | ||
| final version = json['schemaVersion'] as int?; | ||
| if (version != 6 && version != 7 && version != 8 && version != 9) { | ||
| if (version != 10) { |
| 'routeChangeSchema': tugboatRouteChangeSchemaVersion, | ||
| if (data['fromRoute'] != null) 'fromRoute': data['fromRoute'], | ||
| if (data['route'] != null) 'route': data['route'], | ||
| if (data['navigation'] != null) 'navigation': data['navigation'], | ||
| }, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 62 changed files in this pull request and generated no new comments.
Suppressed comments (5)
packages/tugboat/lib/src/viewport_semantic_session.dart:126
- The reset is reached only after the controller has successfully built a non-null scene inventory.
_maybeEmitSceneInventoryreturns before calling this method whenbuildSceneInventoryreturns null (controller.dart:3911-3916), so an unavailable inventory atscroll_startleaves the previous gesture's accumulator alive and a later update can stitch slices across two gestures. Reset fromrecordScrollStartbefore inventory construction (for example through a dedicated session API); the new unavailable-path test currently passes a stale non-null inventory and does not cover this case.
if (scrollContext?.trigger == 'scroll_start') {
_beginScrollSemanticGesture(
routeKey: inventory.routeKey,
scroll: scrollContext!,
);
packages/tugboat/test/viewport_semantic_map_test.dart:210
- This assertion only proves that a tap interaction was emitted; it still passes if tap resolution continues using the stale top-button semantic map and never rebuilds after the same-route widget change. Assert that a second semantic-map event is emitted and that its nodes link the interaction's new target fingerprint so this test actually guards the advertised rebuild behavior.
packages/tugboat/test/replay/replay_navigation_interaction_matrix_test.dart:10 - The replacement leaves only a direct-controller harness case and removes the real
Navigatorcoverage for named push, replacement, pop, andpushNamedAndRemoveUntil. The overlay suite still exercises widget-backed push/pop, but no widget-backed test now covers replacement or stack cleanup, so regressions in observer callback ordering for those operations would pass despite this PR changing route ownership and causal interaction publication. Retain focused real-widget tests for at least replacement and remove-until.
packages/tugboat/CHANGELOG.md:11 - These breaking removals are placed under
Unreleased, while the package is being bumped to 0.8.0 and a separate 0.8.0 section follows. Published 0.8.0 release notes would therefore imply that the removed publication modes, aliases, and constructors are not part of 0.8.0. Merge this breaking-changes subsection into the 0.8.0 entry.
## Unreleased
### Breaking changes
- The SDK now publishes only schema-v2 canonical `interaction` gesture events.
docs/design/capture-and-fingerprint.md:205
- This says state anchors and signatures remain as internal legacy model data, but this PR deletes
TugboatStateAnchorand its resolver/model implementation entirely. Update the design document to describe full removal rather than internal retention.
Version 0.8.0 does not write state identity. State anchors and signatures remain
internal legacy model data only. New event, inventory, semantic-map, diagnostic,
debug, and provenance JSON omit them. Use route evidence, target anchors, and
frame hashes for raw replay facts.
Summary
session_start.stateAnchor,stateSignature, andstate_changedata.tugboatandtugboat_dioas version 0.8.0.Compatibility
stateAnchorfield.Review fixes
user_changedidentity clears.causeEventIdin the flat collector envelope.Validation
flutter analyzeflutter test --concurrency=1 test/collector_mapper_test.dart test/replay/replay_overlay_nested_navigation_matrix_test.dart test/viewport_semantic_map_test.dart(37 passed)git diff --checkThe persistent JPEG encoder adds an asynchronous stage after
endOfFrame. Some existing broad widget tests can inspect results before the isolate reply. The same timing failures reproduce onorigin/main. The new regression tests use deterministic capture seams and pass.