Skip to content

feat(studio): track completed preview editing gestures - #4916

Merged
miguel-heygen merged 18 commits into
mainfrom
feat/studio-feature-used
Oct 3, 2026
Merged

miguel-heygen merged 18 commits into
mainfrom
feat/studio-feature-used

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

What

Track completed preview editing gestures through studio:feature_used. Add missing successful keyframe actions through studio:keyframe and custom inspector inputs through studio:design_input. Replace user-authored route text in the shared Studio telemetry envelope with a route kind and stable equality ID.

Why

Preview gestures and several keyframe write paths bypass existing usage events. A resolved save promise alone does not establish that source changed.

Related work

No linked issue. Existing inspector, keyboard, clipboard, and history events retain their own event names.

How

Carry the writer outcome through geometry commits. Collect grouped GSAP writes into one gesture outcome, then enqueue one usage event. Usage-specific properties are feature, surface and method. Keyframe events include an action and, for the existing single-property add path, a property name. Batch writers return an ordered change receipt for each mutation, so a changed update cannot credit a no-op add. Recording retains its original selection and animation set through the write facade.

Every studio:* event inherits url_hash as project, home or other. url_route_id is now a random eight-hex token generated with crypto.getRandomValues, reused through a route-to-ID map in sessionStorage. The map stays local; neither transport sends route names or selection queries. Independent tabs receive separate tokens. A tab opened from Studio can inherit its opener's sessionStorage tokens, preserving equality within the same session.

Saved insight compatibility was checked through the read-only paginated API. Read 404s: hashchange (door 2) vs mid-session (door 3) compares the loaded route against later events and will use url_route_id for equality. Top diverging targets (triage) currently displays url_hash as a sample and will display the route kind. Their query updates are planned after merge.

Paths below are relative to packages/studio/src/.

Feature Surface Method Commit point
move preview drag components/editor/useDomEditOverlayGestures.ts:357,494
resize preview drag components/editor/useDomEditOverlayGestures.ts:541
rotate preview drag components/editor/useDomEditOverlayGestures.ts:447
crop preview drag, button components/editor/DomEditCropHandles.tsx:239
nudge preview keyboard components/editor/useDomEditNudge.ts:177
multi_select preview drag, button components/editor/marqueeCommit.ts:145, hooks/usePreviewInteraction.ts:173
z_order preview button components/editor/useDomEditZOrder.ts:177
text_edit preview field hooks/useDomEditTextCommits.ts:281
gesture_recording preview button, keyboard hooks/useGestureCommit.ts:355
motion_path preview drag, button components/editor/motionPathCommit.ts:23, components/editor/MotionPathOverlay.tsx
snapping preview button, keyboard components/editor/SnapToolbar.tsx:40,57
grid preview button, keyboard components/editor/SnapToolbar.tsx:45,62
grid_spacing preview field components/editor/SnapToolbar.tsx:27
snap_to_grid preview button components/editor/SnapToolbar.tsx:226
ruler preview button components/editor/SnapToolbar.tsx:122
safe_margins preview button components/editor/SnapToolbar.tsx:122

studio:keyframe actions: add, convert, remove_all, reset. Legacy single/batch writers count changed results; SDK operations compare committed source. Replacement operations that insert a stop carry explicit add metadata. Timeline delete-all counts one gesture across its animation writes.

Missing inspector controls covered: FX parameters, effect bypass, add/remove/reorder effects, apply/remove presets, preset amount, add/edit/remove EQ and automation, native carve controls, levelling enable/remove, and multi-selection group/hide buttons. These use studio:design_input, not studio:feature_used.

Source inventory found no separate preview align/distribute action or custom guide-drag writer.

Test plan

  • Unit tests added/updated
  • All touched-path tests completed
  • Independent review and simplification for the latest random-ID head
  • Manual testing performed
  • Documentation updated (if applicable)
  • New comments explain behavior constraints

Remote Linux: the gesture implementation passed 624 tests across 34 files at 8dfe0571, followed by exact-head affected-hook tests and real canvas/keyframe captures at later heads. Recording target, consumer attribution, producer receipt and nudge mutations each failed their witness test, then passed after restoration. The random-ID follow-up passed 47 telemetry, routing and client tests at e8a63efb. Restoring FNV deliberately failed both complete serialized-batch privacy tests and the independent-tab test; restoring random IDs passed all 12 telemetry tests. At 5995525d, all 47 tests passed again, both typechecks, formatting of 83 touched paths, lint, Fallow audit and production builds passed. The deliberate FNV mutation failed the same three privacy tests, then all 12 telemetry tests passed after restoration. Fresh browser captures show one move and one add event with the same random route ID.

Size

The patch spans gesture callers and their shared save-result plumbing. Group move was extracted to retain the production file-size limit. The random route-ID implementation adds no dependency. Storage decoding and tuple validation are separate from token creation, addressing the Fallow complexity finding without a suppression. Independent source review and all three simplification reviews found no blockers at 5995525d. Final runtime proof passed. Review and simplification marks are recorded at this head. CI is in progress.

Before

On main (d5b56426), the repository edit-accuracy fixture completes the canvas drag and a keyframe add, but neither matching usage event appears in the flushed telemetry batch. Telemetry is enabled and a startup batch is observed.

Before canvas drag, main d5b56426, zero matching usage events

Before keyframe add, main d5b56426, zero matching add events

After

At 5995525d, the same fixture drag persists and emits exactly one studio:feature_used with feature=move, surface=preview, method=drag. A keyframe add persists and emits exactly one studio:keyframe with action=add. Both actual request panels show url_hash=project and the same random eight-hex route ID. Requests are fulfilled locally, so validation does not send analytics. The complete serialized-batch regressions separately check the entire envelope for raw names, encoded names, selection queries and the old deterministic fixture fingerprint.

After canvas drag at 5995525d, one move event with random route identity

After keyframe add at 5995525d, one add event with the same random route identity

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Edit accuracy: accurate 1216 (base branch 1216), smooth 1136 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (1)

@miguel-heygen
miguel-heygen force-pushed the feat/studio-feature-used branch from 1874690 to dcfe83b Compare October 2, 2026 19:55
@miguel-heygen
miguel-heygen marked this pull request as ready for review October 2, 2026 22:16

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested at 944744efa6329705eb228b78e848b76f2a745275 for the stated no-user-content telemetry contract.

New usage events include the user-authored project name in their serialized payload. trackPreviewFeatureUsed (packages/studio/src/utils/previewFeatureUsage.ts:28-30) calls trackStudioEvent, which adds getSessionProperties() to every event (studioTelemetry.ts:44-64,69-78) before drainBatch() forwards those properties to PostHog (studioTelemetry.ts:86-93,107-112). Its url_hash is the hash route with #project/ removed and the query stripped—not an anonymous route kind. Project routing encodes the user-supplied name into that hash (projectRouting.ts:45-56), and the existing route test proves "Launch #1? v2" becomes #project/Launch%20%231%3F%20v2 (projectRouting.test.ts:70-86). A completed preview move in that project therefore sends a new studio:feature_used event with url_hash: "Launch%20%231%3F%20v2", a reversibly encoded project name, alongside feature/surface/method. The same inherited property reaches new keyframe and design-input events. The privacy opt-out stops collection when disabled; it does not remove this content when enabled.

The shared session property predates this PR, but these newly introduced event types inherit it, contrary to the PR's "Send feature, surface and method only" claim and the requested no-user-content payload. Please remove or anonymize project names for these events (or explicitly settle a policy exception), and test the serialized batch using a named project rather than asserting only the caller's filtered properties. I found no other verified code blocker in the gesture/write-result paths. At review time the active 20-shard edit-accuracy and Windows reruns were still in progress; this finding is independent of CI.

— Review by tai (pr-review)

@miguel-heygen

miguel-heygen commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

@terencecho Replaced the deterministic route fingerprint at 5995525dc47a792c645d407c542ca7b34a4e7521 with random eight-hex IDs generated by crypto.getRandomValues. A route-to-ID map remains only in sessionStorage, so the same route reuses its token within a tab without a name-derived value on the wire. Added tests for same-tab reload reuse, separate independent tabs, and complete serialized fetch/beacon batches without raw names, encoded names or the previous fixture fingerprint. Tabs opened from Studio may inherit the opener’s random map. Formatting and lint passed remotely. The random-ID tests passed remotely at the previous head, and restoring FNV made both serialized-batch tests and the independent-tab test fail. The latest commit separates storage decoding from token creation to address the Fallow complexity gate. At this exact head, remote validation passed 47 tests, both typechecks, formatting, lint, Fallow and production builds. Restoring FNV failed the three privacy regressions; restoration passed all 12 telemetry tests. Fresh captures show one move and one keyframe add with the same random route ID. Independent review and three simplification passes found no blockers. CI is in progress.

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested at 07e88fdb10412742e968dca09307d133e9261111 on the remaining no-user-content telemetry contract.

The raw route was removed, but every Studio event now sends a deterministic fingerprint of the authored project name. packages/studio/src/utils/studioTelemetry.ts:51-57 computes an unsalted, 32-bit FNV-1a hash over location.hash before ?; :59-77,83-106 adds that url_route_id to every queued event and serializes it in both transports. The route includes the URL-encoded project name. For the test fixture Launch #1? v2, the client hashes #project/Launch%20%231%3F%20v2 into 7aef12cc; anyone with a candidate project name can compute the same value offline and match it to events. This is a stable, low-entropy pseudonym, not anonymization of user-authored content. The same issue affects non-project hashes categorized as other, which the helper hashes without checking their contents. The new serialized-batch tests (studioTelemetry.test.ts:90-151) assert the raw and URL-encoded name are absent, but explicitly accept the derived eight-hex token, so they do not pin the stated privacy invariant.

The finite url_hash route kind fixes the original plaintext leak, and I found no separate gesture/write-path regression in this two-commit delta. Please omit the deterministic name-derived url_route_id, use a random per-session/per-tab route-equality token if the mid-session comparison needs one, or obtain an explicit privacy-policy exception for a stable cross-session project fingerprint. Test the complete serialized fetch and beacon batches against the derived-token case, not just the raw name.

Verdict: REQUEST CHANGES
Reasoning: The new event sink no longer exposes the project name directly, but the replacement still emits a reproducible identifier derived from that name; one approval would be merge-enabling before this contract is resolved.

— Review by tai (pr-review)

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at 5995525dc47a792c645d407c542ca7b34a4e7521 after re-reviewing the three-commit privacy fix against my prior changes request.

The unsalted FNV project-name fingerprint is gone. studioRouteId now maps each route (without its query) to four CSPRNG bytes; route strings are keys only in sessionStorage/the in-memory map, while the queued studio:* event's route properties carry only the finite kind and random eight-hex token. Both fetch and beacon serialize that same event shape. The serialized-batch tests reject the original project's raw and URL-encoded name and its old deterministic token, and exercise route equality across query changes and a reload. I found no remaining blocker in this delta against the route-envelope privacy contract.

The Studio test, lint, typecheck, and preflight checks passed when I inspected them; edit-accuracy and Windows shards were still pending, so this review does not claim the entire CI run is green.

— Review by tai (pr-review)

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit 852782b Oct 3, 2026
159 of 160 checks passed
@miguel-heygen
miguel-heygen deleted the feat/studio-feature-used branch October 3, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants