Search existing issues
Describe the bug
On macOS, a themed cursor can remain stuck on its last semantic cursor type (pointer or text) after the real cursor has returned to the normal arrow state.
I reproduced this with the Hello Kitty & Watermelon theme in OpenScreen 1.10.0:
- the normal arrow sprite is the watermelon;
- the
pointer sprite is Hello Kitty;
- after the recorded cursor leaves a clickable element, the preview can keep rendering Hello Kitty instead of returning to the watermelon arrow;
- once this happens, moving the playhead or reselecting the same theme does not reliably restore the arrow sprite.
The problem became visible immediately after an AI edit finished removing unused sections and adding zooms. However, inspecting the saved .openscreen project showed that the agent had not changed legacyEditor.cursorTheme; it remained hello-kitty-watermelon. The agent only added trims and zooms.
This is therefore different from #284: the theme was selected before the agent turn, no cursor-theme edit was made while the agent was running, and the saved theme value was not overwritten.
Expected behavior
When macOS reports that the current cursor is no longer a special semantic type, the compositor should return to the captured/default arrow bitmap. With this theme, leaving a clickable element should switch from Hello Kitty back to the watermelon arrow.
The theme picker should also make it clear when a theme uses different artwork for arrow and pointer, instead of previewing only the arrow sprite.
To Reproduce
- Record a macOS screen interaction where the cursor moves from a normal area onto a clickable element and then back to a normal area.
- Open the recording in OpenScreen 1.10.0.
- Select the Hello Kitty & Watermelon cursor theme.
- Ask the AI editor to remove unused sections and add local zooms.
- Wait for the agent turn to finish and preview the edited timeline.
- Observe that the cursor can change to the Hello Kitty
pointer sprite and remain there after the recorded cursor has left the clickable element. The inspector still shows the same theme selected.
The lower-level failure does not appear to depend on the AI edit itself: any cursor telemetry sequence equivalent to null/arrow -> pointer -> null/arrow can expose it.
Screenshots
Not attached because the reproduction recording contains private application content. The state transition and source-level cause below are deterministic.
OS
macOS
OS Version
macOS 15.4.1 (24E263)
Browser
Not applicable — OpenScreen desktop app.
Device Type
Laptop (MacBook Air, Mac16,12)
Additional context
There appears to be a semantic mismatch between the macOS cursor helper and the Rust compositor:
-
The macOS helper returns nil for cursor states other than I-beam and pointing hand, with the comment that the renderer should fall through to the captured/default cursor bitmap:
https://github.com/getopenscreen/openscreen/blob/main/electron/native/screencapturekit/Sources/OpenScreenMacOSCursorHelper/main.swift#L224-L231
-
The compositor's cursor type track ignores samples whose cursorType is absent. type_at(t) therefore returns the last explicit semantic type at or before t, allowing pointer or text to persist across later null samples:
https://github.com/getopenscreen/openscreen/blob/main/crates/compositor/src/cursor.rs#L100-L140
In the real .cursor.json sidecar used for this reproduction, the raw transitions included sequences such as:
2136 ms: pointer
2173 ms: null
2214 ms: pointer
2494 ms: null
Applying the current type_at logic means the 2173 ms null sample does not reset the state; the effective type remains pointer.
The selected theme deliberately maps the semantic types to different sprites:
https://github.com/getopenscreen/openscreen/blob/main/src/lib/cursor/cursorThemes.ts#L89-L113
The renderer resolves the full sprite set per semantic cursor type:
https://github.com/getopenscreen/openscreen/blob/main/src/lib/cursor/cursorThemes.ts#L480-L510
The theme picker, however, previews only theme.assets.arrow (falling back to pointer), so users see the watermelon during selection but are not shown that the same theme uses Hello Kitty for pointer:
https://github.com/getopenscreen/openscreen/blob/main/src/components/ai-edition/RightPanes.tsx#L2324-L2340
Possible fix direction: represent the normal arrow/fallback as an explicit reset transition in the cursor type track, or make type_at treat a macOS null sample as a reset rather than filtering it out. A regression test for pointer -> null -> arrow fallback would cover the failure.
Related but different:
Search existing issues
Describe the bug
On macOS, a themed cursor can remain stuck on its last semantic cursor type (
pointerortext) after the real cursor has returned to the normal arrow state.I reproduced this with the Hello Kitty & Watermelon theme in OpenScreen 1.10.0:
pointersprite is Hello Kitty;The problem became visible immediately after an AI edit finished removing unused sections and adding zooms. However, inspecting the saved
.openscreenproject showed that the agent had not changedlegacyEditor.cursorTheme; it remainedhello-kitty-watermelon. The agent only added trims and zooms.This is therefore different from #284: the theme was selected before the agent turn, no cursor-theme edit was made while the agent was running, and the saved theme value was not overwritten.
Expected behavior
When macOS reports that the current cursor is no longer a special semantic type, the compositor should return to the captured/default arrow bitmap. With this theme, leaving a clickable element should switch from Hello Kitty back to the watermelon arrow.
The theme picker should also make it clear when a theme uses different artwork for
arrowandpointer, instead of previewing only the arrow sprite.To Reproduce
pointersprite and remain there after the recorded cursor has left the clickable element. The inspector still shows the same theme selected.The lower-level failure does not appear to depend on the AI edit itself: any cursor telemetry sequence equivalent to
null/arrow -> pointer -> null/arrowcan expose it.Screenshots
Not attached because the reproduction recording contains private application content. The state transition and source-level cause below are deterministic.
OS
macOS
OS Version
macOS 15.4.1 (24E263)
Browser
Not applicable — OpenScreen desktop app.
Device Type
Laptop (MacBook Air, Mac16,12)
Additional context
There appears to be a semantic mismatch between the macOS cursor helper and the Rust compositor:
The macOS helper returns
nilfor cursor states other than I-beam and pointing hand, with the comment that the renderer should fall through to the captured/default cursor bitmap:https://github.com/getopenscreen/openscreen/blob/main/electron/native/screencapturekit/Sources/OpenScreenMacOSCursorHelper/main.swift#L224-L231
The compositor's cursor type track ignores samples whose
cursorTypeis absent.type_at(t)therefore returns the last explicit semantic type at or beforet, allowingpointerortextto persist across laternullsamples:https://github.com/getopenscreen/openscreen/blob/main/crates/compositor/src/cursor.rs#L100-L140
In the real
.cursor.jsonsidecar used for this reproduction, the raw transitions included sequences such as:Applying the current
type_atlogic means the 2173 msnullsample does not reset the state; the effective type remainspointer.The selected theme deliberately maps the semantic types to different sprites:
https://github.com/getopenscreen/openscreen/blob/main/src/lib/cursor/cursorThemes.ts#L89-L113
The renderer resolves the full sprite set per semantic cursor type:
https://github.com/getopenscreen/openscreen/blob/main/src/lib/cursor/cursorThemes.ts#L480-L510
The theme picker, however, previews only
theme.assets.arrow(falling back topointer), so users see the watermelon during selection but are not shown that the same theme uses Hello Kitty forpointer:https://github.com/getopenscreen/openscreen/blob/main/src/components/ai-edition/RightPanes.tsx#L2324-L2340
Possible fix direction: represent the normal arrow/fallback as an explicit reset transition in the cursor type track, or make
type_attreat a macOSnullsample as a reset rather than filtering it out. A regression test forpointer -> null -> arrow fallbackwould cover the failure.Related but different: