@@ -5,9 +5,9 @@ checkpoints around meaningful interactions, compact structural anchors, route
55transitions, scrolling evidence, and optional viewport semantic maps. Capture
66can be sent to the local exploration WebSocket, the HTTP collector, or both.
77
8- The current package version is ` 0.8.0 ` . Session JSON writers emit schema
9- version ` 10 ` ; compatibility readers should accept versions ` 6 ` through
10- ` 10 ` . Structural fingerprints use fingerprint schema version ` 6 ` .
8+ The current package version is ` 0.8.0 ` . Session JSON writers and readers use
9+ schema version ` 10 ` only. Structural fingerprints use fingerprint schema
10+ version ` 6 ` .
1111
1212## 0.8.0 raw-event compatibility
1313
@@ -182,7 +182,7 @@ Navigator/repaint-boundary contract.
182182Each visible route change creates a route epoch and waits for the transition
183183plus the configured settle delay before taking the destination capture. A newer
184184visible route supersedes an older pending capture. Consequently, a route event
185- and a related `tap_settled` event are intended to reference a frame compatible
185+ and a related canonical `interaction` are intended to reference a frame compatible
186186with that route epoch, or report bounded degraded/capture diagnostics rather
187187than attach an origin-route frame merely because it was the latest frame.
188188
@@ -191,30 +191,12 @@ Production acceptance for #13/#14 remains open: rapid or nested modal chains
191191and programmatic/automatic navigation can still be absent or degraded. Treat
192192those cases as an SDK capture gap, not as coherent replay evidence.
193193
194- # ## Deferred taps and interaction claims
194+ # ## Interaction claims
195195
196- ` tap` is sampled at pointer-down but emitted only after gesture classification
197- (or when a claimed `route_change` must publish a cause). Consumers must read :
198-
199- | Field | Meaning |
200- | --- | --- |
201- | `data.replayRole` | `interaction` (real user tap) or `causal_only` (cause id for a route; not a replayable action) |
202- | `data.gestureFinal` | `tap`, `swipe`, `cancelled`, `superseded`, `session_end`, or `unresolved` |
203- | `data.sampledAtMs` | Pointer-down sample time when the event was published later |
204- | `data.captureCoordinate` | Boundary-local / normalized / raster transform for the before-frame |
205- | `route_change.data.navigationOrigin` | `interaction` or `automatic_or_unknown` |
206- | `route_change.data.causeEventId` | Claimed tap id when origin is `interaction` |
207- | `route_change.data.interactionAttribution` | Always `same_turn` when claimed |
208- | `swipe` (legacy dual-write) `.data.invalidatesRelatedTap` | Boolean `true` when a related causal tap should be ignored for playback |
209- | `tap_gesture_resolved` | Promotes `relatedEventId` from `causal_only` → interaction (`promotesRelatedTap`) |
210-
211- Released pointer-up claims attribute a route only through the pointer-up turn
212- (sync `onTap` → `Navigator.push`). Timer/auth redirects after that turn stay
213- ` automatic_or_unknown` . Backgrounding and `session_end` drop pending claims
214- without minting orphan `causal_only` taps for cancelled routes.
215-
216- **Downstream contract:** Context Graph and PMKit CLI must not treat
217- `replayRole : causal_only` taps as real actions or step openers.
196+ Pointer-down freezes interaction origin data. Pointer-up classifies the gesture
197+ and publishes one canonical `interaction`. A claimed `route_change` uses that
198+ interaction ID as `causeEventId`. Released pointer-up claims apply only through
199+ the pointer-up turn. Timer or auth redirects stay `automatic_or_unknown`.
218200
219201# # Capture profiles and runtime state
220202
@@ -250,9 +232,8 @@ Identity contract:
250232- ` explorationRunId` — exploration control-plane ID from config
251233- ` traitsId` — collector-issued traits dictionary id after `setTraits` / session responses
252234
253- ` TugboatReplay.activeSessionId` remains as a deprecated alias for
254- ` activationRequestId` . Inspect `TugboatReplay.health` for sink/outbox/screenshot
255- budget pressure without reading protected content.
235+ Inspect `TugboatReplay.health` for sink, outbox, and screenshot-budget pressure
236+ without reading protected content.
256237
257238# ## User traits and user id
258239
@@ -300,7 +281,6 @@ Call `TugboatReplay.clearDurableOutbox()` on logout/consent revocation.
300281| `profile` | `dormant` | capture cost and exploration-only behavior |
301282| `settleDelay` | 1 second | delay before post-interaction and post-route capture |
302283| `interactionClaimWindow` | 1,250 ms | released-tap window for delayed route/modal attribution; `Duration.zero` keeps microtask-only same-turn claims |
303- | `interactionPublishMode` | `canonicalOnly` | how finalized gestures are published; new recordings emit one canonical `interaction`. `legacyOnly` and `dualWrite` are deprecated compatibility modes |
304284| `maxFrames` | 500 | in-memory frame bound |
305285| `maxEvents` | 5000 | in-memory event bound |
306286| `scrollCaptureInterval` | 2 seconds | interval for scroll checkpoint capture |
@@ -321,46 +301,6 @@ Call `TugboatReplay.clearDurableOutbox()` on logout/consent revocation.
321301| `outbox` | disabled | durable HTTP outbox configuration |
322302| `screenshotBudget` | 60ms / 5s window | degraded-capture skip window / budget |
323303
324- # ## Legacy gesture projection deprecation
325-
326- New recordings default to `TugboatInteractionPublishMode.canonicalOnly`. One
327- completed physical gesture produces one semantic `interaction` event containing
328- its immutable origin, finalized gesture, result, attribution, and evidence IDs.
329- The SDK no longer emits separate `tap`, `tap_settled`, or `swipe` rows unless an
330- integration explicitly opts into a legacy mode.
331-
332- ` dualWrite` and `legacyOnly` remain available temporarily so older collectors,
333- Context Graph revisions, dashboards, and replay fixtures can be migrated without
334- making historical recordings unreadable :
335-
336- - ` canonicalOnly` — supported default for all new recordings;
337- - ` dualWrite` — deprecated migration override that adds legacy peers on
338- `stream : legacy_projection` with `enrichmentCandidate: false`;
339- - ` legacyOnly` — deprecated emergency compatibility override for consumers that
340- cannot yet read canonical `interaction` records.
341-
342- Do not enable either legacy mode in a new application integration. Consumers
343- must use `interaction` as the user action and treat route/frame records as
344- linked evidence. Historical `tap` and `tap_settled` rows may still be read and
345- correlated through `interactionId` / `relatedEventId`, but must not be counted as
346- additional user actions.
347-
348- The code marker `TODO(tugboat-legacy-projection-removal)` tracks final removal.
349- Remove the legacy enum values and emission branches in a future breaking SDK
350- release only after all of the following are true :
351-
352- 1. Supported Collector and Context Graph versions consume canonical
353- ` interaction` records and ignore legacy projections by default.
354- 2. Dashboard, insight, rage-tap, and replay queries no longer depend on
355- ` tap_settled` or legacy `swipe` rows.
356- 3. Production telemetry confirms that current SDK versions are recording
357- canonical interactions successfully across representative tap, navigation,
358- scroll, cancellation, and lifecycle cases.
359- 4. Retained dual-write fixtures remain available to test historical replay
360- compatibility after the emitters are deleted.
361- 5. Release notes announce the removal and the SDK schema/breaking version is
362- advanced deliberately.
363-
364304# ## Resolver and exploration events
365305
366306When exploration is active, the controller may emit :
@@ -371,10 +311,6 @@ When exploration is active, the controller may emit:
371311| `viewport_semantic_map` | Bounded semantic node map (mode-dependent) |
372312| `scroll_semantic_snapshot` | Semantic snapshot tied to scroll checkpoints |
373313| `action_window_set` / `action_window_cleared` | CLI exploration action-window fencing |
374- | `tap_outside_tree` | Pointer resolved no target; carries the same `replayRole` / `gestureFinal` as the paired tap when deferred |
375- | `tap_gesture_resolved` | Promotes a prior `causal_only` tap (`promotesRelatedTap : true`, `relatedEventId`) after the gesture finalizes as a real tap |
376-
377- Consumers that filter `replayRole : causal_only` must honor `tap_gesture_resolved` (or the patched in-memory tap) before suppressing genuine navigations.
378314
379315# # Privacy and payload boundary
380316
@@ -427,9 +363,6 @@ Emitted inferred event types currently include:
427363- canonical : ` interaction` (`stream: semantic`) — one finalized gesture
428364 (`tap`, `swipe`, `scroll`, or `cancelled`) with gesture-specific facts under
429365 ` payload` (omitted for `cancelled`);
430- - deprecated legacy gesture peers (emitted only when an integration explicitly
431- selects `dualWrite` or `legacyOnly`) : ` tap` , `tap_settled`, `swipe`,
432- ` tap_outside_tree` , `tap_gesture_resolved`;
433366- lifecycle : ` session_start` , `session_identify`, `session_end`;
434367- navigation evidence (`stream : evidence`): `route_change`;
435368- diagnostics : ` capture_diagnostic` (`stream: diagnostic`);
@@ -442,7 +375,7 @@ Default enrichment and insight selection should use inferred events:
442375`stream : semantic` `interaction` records (`enrichmentCandidate: true` on
443376collector payloads).
444377Rage-tap style insights must count finalized `gesture=tap` interactions;
445- exclude scrolls, swipes, cancellations, evidence, legacy projections, and
378+ exclude scrolls, swipes, cancellations, evidence, and
446379diagnostics.
447380
448381Frames can be triggered by initial startup, interactions, routes, lifecycle,
@@ -457,25 +390,17 @@ local-WebSocket suppression, paint-generation reuse, dHash reuse, or
457390content-hash reuse. A fresh route capture can satisfy only the interaction that
458391causally claimed it.
459392
460- Pointer coordinates in event data (`x`, `y`, and swipe `startX`/`startY`) are
461- Flutter global logical-pixel coordinates from the pointer event. The SDK
462- converts a copy into its capture boundary's local space only for hit-testing and
463- normalizing target/viewport-semantic bounds; stored event coordinates are not
464- capture-boundary-normalized for replay playback. Do not interpret them as
465- physical pixels or as coordinates relative to an individual widget. Fractional
466- overlay drift is therefore still possible.
393+ Interaction payload coordinates use normalized capture-boundary space. Do not
394+ interpret them as physical pixels or as coordinates relative to a widget.
467395
468396For a tap, origin context (target, `beforeFrame`,
469397` captureCoordinate` , route/navigator identity) is frozen at pointer-down into
470398an `InteractionTransaction`. After pointer-up, settlement waits for either the
471399first eligible visible successor inside `interactionClaimWindow` (default
4724001,250 ms) or the deadline. The canonical `interaction` event retains that
473401frozen origin and attaches destination/result fields when a successor claims.
474- When deprecated dual-write compatibility is explicitly enabled, legacy `tap` +
475- ` tap_settled` records link via `relatedEventId` / `interactionId`. They are not
476- additional semantic actions. A missing attachment is explicit in
477- ` frameAttachment` /settle diagnostics rather than a fallback to an unrelated
478- frame.
402+ A missing attachment remains unavailable. The SDK does not attach an unrelated
403+ frame as a fallback.
479404
480405During local WebSocket exploration, connecting without an HTTP collector
481406suppresses only non-interaction Flutter screenshot capture for UI-thread
0 commit comments