From 34f4d5d0af2d69672419cd3e6767d9a6e31b7b5c Mon Sep 17 00:00:00 2001 From: Mateus Junges Date: Thu, 29 Oct 2026 14:28:46 -0300 Subject: [PATCH 1/3] fix(desktop): recording cursor stays above modal dialogs and popovers --- apps/desktop/src/preview/RecordingCursor.ts | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/preview/RecordingCursor.ts b/apps/desktop/src/preview/RecordingCursor.ts index 5b9fb3c13bc37..cdd6555ef09f1 100644 --- a/apps/desktop/src/preview/RecordingCursor.ts +++ b/apps/desktop/src/preview/RecordingCursor.ts @@ -11,6 +11,11 @@ import { type RecordingKeyPress, } from "./RecordingInput.ts"; +// Modal dialogs, popovers and fullscreen elements paint above any z-index, so the cursors live in +// the top layer as manual popovers and move back to its front whenever the page adds to it. +const POPOVER_RESET = + "margin:0;padding:0;border:0;background:transparent;overflow:visible;inset:auto;"; + /** Draw in guest coordinates; Chromium's native cursor ignores webview CSS placement/scale. Hide it to avoid duplicates. */ export function installRecordingCursor( document: Document, @@ -20,22 +25,43 @@ export function installRecordingCursor( ) { const style = document.createElement("style"); style.textContent = - "html, html * { cursor: none !important; } @media (prefers-reduced-motion: reduce) { [data-t3code-recording-agent-cursor] { transition: none !important; } }"; + "html, html * { cursor: none !important; } [data-t3code-recording-cursor]::backdrop, [data-t3code-recording-agent-cursor]::backdrop { display: none !important; } @media (prefers-reduced-motion: reduce) { [data-t3code-recording-agent-cursor] { transition: none !important; } }"; const cursor = document.createElement("div"); cursor.setAttribute("aria-hidden", "true"); cursor.setAttribute("data-t3code-recording-cursor", ""); + cursor.popover = "manual"; cursor.style.cssText = + POPOVER_RESET + "position:fixed;left:0;top:0;width:16px;height:24px;pointer-events:none;z-index:2147483647;display:none;"; cursor.innerHTML = ''; const agentCursor = document.createElement("div"); agentCursor.setAttribute("aria-hidden", "true"); agentCursor.setAttribute("data-t3code-recording-agent-cursor", ""); + agentCursor.popover = "manual"; agentCursor.style.cssText = + POPOVER_RESET + "position:fixed;left:0;top:0;width:20px;height:20px;pointer-events:none;z-index:2147483647;display:none;filter:drop-shadow(0 1px 2px #0003);transition:transform 150ms ease-out,opacity 150ms ease-out;"; agentCursor.innerHTML = ''; document.documentElement.append(style, cursor, agentCursor); + const cursors = [cursor, agentCursor]; + const raise = () => { + for (const element of cursors) { + if (!element.isConnected || typeof element.showPopover !== "function") continue; + if (element.matches(":popover-open")) element.hidePopover(); + element.showPopover(); + } + }; + raise(); + const dialogObserver = new MutationObserver(raise); + dialogObserver.observe(document, { subtree: true, attributes: true, attributeFilter: ["open"] }); + const toggle = (event: Event) => { + if (cursors.some((element) => element === event.target)) return; + if ("newState" in event && event.newState === "open") raise(); + }; + document.addEventListener("toggle", toggle, true); + document.addEventListener("fullscreenchange", raise); let controller: "human" | "agent" | "none" = "none"; let humanPoint: { readonly x: number; readonly y: number } | null = null; const drawHuman = () => { @@ -182,6 +208,9 @@ export function installRecordingCursor( window.removeEventListener("keyup", keyUp, true); window.removeEventListener("pointerout", leave, true); window.removeEventListener("blur", hide); + dialogObserver.disconnect(); + document.removeEventListener("toggle", toggle, true); + document.removeEventListener("fullscreenchange", raise); cancelPendingPointer(); window.clearTimeout(agentTimer); cursor.remove(); From 306ea0fc32fbbbf0574632f4eced9b60a374b14a Mon Sep 17 00:00:00 2001 From: Mateus Junges Date: Thu, 29 Oct 2026 14:39:15 -0300 Subject: [PATCH 2/3] fix(desktop): page popover animations no longer move the recording cursor --- apps/desktop/src/preview/RecordingCursor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/preview/RecordingCursor.ts b/apps/desktop/src/preview/RecordingCursor.ts index cdd6555ef09f1..b7ab1bb1a599e 100644 --- a/apps/desktop/src/preview/RecordingCursor.ts +++ b/apps/desktop/src/preview/RecordingCursor.ts @@ -14,7 +14,7 @@ import { // Modal dialogs, popovers and fullscreen elements paint above any z-index, so the cursors live in // the top layer as manual popovers and move back to its front whenever the page adds to it. const POPOVER_RESET = - "margin:0;padding:0;border:0;background:transparent;overflow:visible;inset:auto;"; + "margin:0;padding:0;border:0;background:transparent;overflow:visible;inset:auto;animation:none;transition:none;"; /** Draw in guest coordinates; Chromium's native cursor ignores webview CSS placement/scale. Hide it to avoid duplicates. */ export function installRecordingCursor( From 52e62445f3347916aabd04f6931261d4e8bc31f3 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Fri, 30 Oct 2026 11:49:22 +0000 Subject: [PATCH 3/3] fix(desktop): reset page styles on recording cursors --- apps/desktop/src/preview/RecordingCursor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/preview/RecordingCursor.ts b/apps/desktop/src/preview/RecordingCursor.ts index b7ab1bb1a599e..29f1865de291f 100644 --- a/apps/desktop/src/preview/RecordingCursor.ts +++ b/apps/desktop/src/preview/RecordingCursor.ts @@ -14,7 +14,7 @@ import { // Modal dialogs, popovers and fullscreen elements paint above any z-index, so the cursors live in // the top layer as manual popovers and move back to its front whenever the page adds to it. const POPOVER_RESET = - "margin:0;padding:0;border:0;background:transparent;overflow:visible;inset:auto;animation:none;transition:none;"; + "all:initial;margin:0;padding:0;border:0;background:transparent;overflow:visible;inset:auto;animation:none;transition:none;"; /** Draw in guest coordinates; Chromium's native cursor ignores webview CSS placement/scale. Hide it to avoid duplicates. */ export function installRecordingCursor(