macOS: Pet control icons separate from native circular backgrounds while dragging
What version of the Codex App are you using (From “About Codex” dialog)?
Codex Desktop 26.727.51351 (build 6119)
What subscription do you have?
Not relevant to this local UI rendering issue.
What platform is your computer?
macOS 26.6 (25G72), Apple Silicon (arm64)
What issue are you seeing?
When the floating Pet is dragged, the Pet sprite and the dark control glyphs move with the pointer, but the white circular backgrounds behind the controls remain at stale screen coordinates. This separates the glyphs from their circles vertically and sometimes horizontally.
The problem affects both:
- the built-in Oli pet;
- a valid custom V2 pet.
Both the voice/waveform control and the activity-count badge drift together, so this does not appear to be sprite padding or custom-pet atlas geometry.
The controls may realign after the hover state is recreated, tucking and waking the Pet, or restarting Codex, but the issue recurs after dragging.
What steps can reproduce the bug?
- Wake a floating Pet on macOS.
- Have at least one active/recent activity so the waveform control and numbered activity badge are visible.
- Hover the Pet so the circular controls are shown.
- Click and drag the Pet to another position while the controls remain visible.
- Release the pointer and observe the controls.
What is the expected behavior?
The control glyphs and their circular backgrounds should move as a single unit and remain centered throughout and after the drag.
What is the actual behavior?
The Pet sprite and glyph layer track the drag, while the circular background layer stays behind or updates late. The two layers remain visibly separated after release in some positions.
Additional information
- Reproduced with the notification stack collapsed.
- A screen recording captured the issue continuously on the built-in Oli pet.
- The recording is not attached here because this is a public tracker and the original capture contains unrelated workspace content; it can be provided privately if maintainers need it.
- Local diagnostics at the reproduction time report
nativeAttached=truefor bothvoice-controlsandmascot-badge, whileisNotificationStackExpanded=false. - This suggests that the Chromium glyph/overlay position and the macOS native material backing position are not being refreshed synchronously during Pet dragging.
1 Comment
Additional deterministic trigger, code-path evidence, and proposed fix
I can reproduce this reliably by dragging the Pet close to the macOS top edge/menu-bar boundary. At normal positions, the waveform/count glyphs and their native circular backgrounds are concentric. Near the top edge, both native circles remain approximately one menu-bar inset away from the Pet while the Chromium glyphs initially continue moving with the Pet; the glyphs may catch up later.
On this Retina 2x display, the displacement is approximately 78 physical pixels = 39 logical pixels.
NSScreenreports a 1329-point full frame and a 1290-point visible frame, also a 39-point difference. This makes the deterministic boundary related to the menu-barvisibleFrame/ ElectronworkAreainset rather than sprite padding or an individual icon's layout.Build 6119 code-path findings
The installed application bundle contains the relevant Desktop implementation even though it is not present in the public repository source tree:
getLayoutForDisplay()passes a display rect based ondisplay.workArea, preservingworkArea.yas the top bound. The anchor clamp then clampsyto that bound.moveDrag()skips the renderer-drag path.updateWindowServerDragPlacement()updates the anchor, but if the logical top/bottom placement is unchanged and the update is not final, it returns before recomputing the full layout, callingcompositionHost.updateMascotRect(), or sending renderer state.compositionHost.updateMascotRect()also has a separate equality early return. Moving the root window changes its screen origin but does not change the mascot's window-local rect, so merely calling this method again is not necessarily enough to refresh native child-window geometry.voice-controlsandmascot-badgefromchromiumPresentationRectrelative to the mascot. When native material is attached, the DOM control background is transparent, confirming that the visible circular backing is supplied by a separate native material layer.The compiled
avatar-overlay.nodealso preserves the relevant Objective-C method names:-[AvatarOverlayRootEffectSurface updateVoiceControlsInteractionScreenRect:surfaceRects:... ]independently sets frames forvoiceControlsInteractionWindowandvoiceControlsGlassWindow.-[AvatarOverlayRootEffectSurface windowDragWillBegin]marks the native drag active and stops the current glass-frame animation.-[AvatarOverlayRootEffectSurface windowDidMove:]returns whilewindowDragInProgressis true.-[AvatarOverlayCompositionCoordinator updateVoiceControlsPresentationFrames:]later publishes control screen rects to the Chromium surfaces.-[AvatarOverlayCompositionSurface applyPetControlsPresentationPositionForScreenRect:]computes the screen-rect center delta and applies transforms separately to the container and Chromium layers.This sequence explains both parts of the symptom: the root/Chromium windows move past the last work-area-clamped position while the independent native glass frames remain stale, and the glyphs later move again when the native presentation-frame callback applies a corrective transform. The exact 39-point maximum separation is the distance from the last valid work-area origin to the display edge.
Proposed fix
The preferred fix is to give the native glass/interaction windows and Chromium surfaces one canonical screen-space control-rect update per native-drag frame:
AvatarOverlayRootEffectSurface.windowDidMove:(or the existingdisplayLinkDidFire:), refreshvoiceControlsGlassWindowandvoiceControlsInteractionWindowfrom the current root-window screen origin even whilewindowDragInProgressis true. Expensive wallpaper alignment may remain deferred.voiceControlsGlassScreenFramesChangedHandlerwith those same rects so the Chromium presentation surfaces receive the identical geometry in the same display tick.updateWindowServerDragPlacement(), do not treat an unchanged logical placement as unchanged geometry. If root content bounds or screen origin changed, invoke an explicit nativesyncWindowGeometry()operation before returning.updateMascotRect(), because its input is window-local and its equality optimization can suppress the required screen-origin refresh.A lower-risk temporary workaround would be to disable the Window Server drag path while Pet controls are visible and use the existing
moveDragToPointer()->applyLayout()path, which already applies the work-area clamp and refreshes all layout consumers together.Suggested regression coverage:
workArea.minYand assert on every frame that the native glass-frame center and Chromium presentation-frame center for bothvoice-controlsandmascot-badgediffer by less than 0.5 logical pixels.updateWindowServerDragPlacement(): changed root bounds must trigger native geometry synchronization.Public repository check
I checked current
openai/codexmainatf94b5d899ae8e2785f4f028d35055d4be6b9dca3(2026-08-03). The repository contains TUI Pet code undercodex-rs/tui/src/pets/*and Desktop launcher code undercodex-rs/cli/src/desktop_app/*, but not the Electron/native Desktop overlay implementation. Repository-wide searches forAvatarOverlayRootEffectSurface,updateVoiceControlsPresentationFrames,mascot-badge,BrowserWindow, andworkAreareturn no matches. The maintainers will therefore need to map the build-level function and class names above to the internal Desktop source tree.