Summary
patch_tray_inplace_update in scripts/patches/tray.sh stopped applying on Claude Desktop 1.13576+ (the "yukonSilver"-era refactor; verified against the current detect-host.sh target 1.15962.0). Because tray patches warn-and-continue (the #429 class), it ships through green CI — the in-place fast-path is simply gone, which re-arms the #515 KDE/Plasma duplicate-icon StatusNotifier race documented in docs/learnings/tray-rebuild-race.md.
Surfaced while investigating #746 (Cinnamon empty-icon). Note: this is not the root cause of #746 — the core icon-selection patch still applies on both 1.15200.0 and 1.15962.0 — but it's a real latent regression found in the same area.
What changed upstream
Extracted the real 1.15962.0 app.asar and ran tray.sh's extraction against the shipped minified index.js.
1. The destroy/recreate guard restructured. Old shape the injection anchored on:
;if(TRAY&&(TRAY.destroy(),TRAY=null)…
New shape (state resets hoisted into the if):
const t=X.join(toi(),e),i=!xrt;if(A9=[],e9=!1,vE&&(vE.destroy(),vE=null),!A){JhA();return}
The regex anchor ;if\(TRAY&&\(TRAY\.destroy\(\) no longer matches.
2. Context menu is now a prebuilt object, with a null decoy. Old: setContextMenu(BUILDER()). New: the menu is built once (yh=S5A()) and set by name, and the first setContextMenu call site is a menu-clear:
…vE.setContextMenu(null)… // invalidation clear ← FIRST in file order
…yh=S5A(),…vE.setContextMenu(yh)… // the real one; builder is S5A()
The resolver's head -1 latched the null clear, so the builder never resolved → the #680 WARNING: could not resolve tray menu function fires and the fast-path is skipped.
Not a regression: patch_menu_bar_default
Worth recording so it isn't "fixed" twice: upstream now reads menuBarEnabled through a settings getter backed by a defaults map that already ships menuBarEnabled:!0 (true):
Di=A=>{const e=tg().preferences??{}; …; return {...Bpt,...e,...A}[A]}
Bpt={menuBarEnabled:!0, …}
So the legacy !!var → var!==false rewrite is a no-op by design, not a behavior regression — the tray still defaults on.
Fix
On branch claude/review-open-issues-prs-d4cjs6 (commit 6091615):
- Menu-function resolver walks every
setContextMenu argument, skips the null clear, and takes the first that resolves to a VAR=BUILDER() assignment (→ S5A).
- Injection switched from the backslash-heavy regex to locating
TRAY.destroy() and walking back to the ;if( that opens its statement — lands correctly in both old and new shapes.
patch_menu_bar_default now distinguishes the upstream-defaults-map case from a genuine miss, warning loudly only if neither the legacy anchor nor menuBarEnabled:!0 is present.
- New
tests/tray-patches.bats (7 cases): builder resolution past the null decoy, injection placement + valid JS, idempotency, loud-fail on a missing destroy site, and the three menu-bar-default branches. Full suite 329/329.
Verified end-to-end against the real 1.15962.0 asar (patched output passes node --check, fast-path lands before the destroy block, idempotent on re-run).
Remaining
The whole point of the fast-path is StatusNotifier timing on Plasma, so runtime confirmation of the #515 race avoidance still needs a KDE Plasma host before merge. Flagging for on-device verification.
Written by Claude via Claude Code
Summary
patch_tray_inplace_updateinscripts/patches/tray.shstopped applying on Claude Desktop 1.13576+ (the "yukonSilver"-era refactor; verified against the currentdetect-host.shtarget 1.15962.0). Because tray patches warn-and-continue (the #429 class), it ships through green CI — the in-place fast-path is simply gone, which re-arms the #515 KDE/Plasma duplicate-icon StatusNotifier race documented indocs/learnings/tray-rebuild-race.md.Surfaced while investigating #746 (Cinnamon empty-icon). Note: this is not the root cause of #746 — the core icon-selection patch still applies on both 1.15200.0 and 1.15962.0 — but it's a real latent regression found in the same area.
What changed upstream
Extracted the real
1.15962.0app.asarand rantray.sh's extraction against the shipped minifiedindex.js.1. The destroy/recreate guard restructured. Old shape the injection anchored on:
New shape (state resets hoisted into the
if):The regex anchor
;if\(TRAY&&\(TRAY\.destroy\(\)no longer matches.2. Context menu is now a prebuilt object, with a
nulldecoy. Old:setContextMenu(BUILDER()). New: the menu is built once (yh=S5A()) and set by name, and the firstsetContextMenucall site is a menu-clear:The resolver's
head -1latched thenullclear, so the builder never resolved → the #680WARNING: could not resolve tray menu functionfires and the fast-path is skipped.Not a regression:
patch_menu_bar_defaultWorth recording so it isn't "fixed" twice: upstream now reads
menuBarEnabledthrough a settings getter backed by a defaults map that already shipsmenuBarEnabled:!0(true):So the legacy
!!var→var!==falserewrite is a no-op by design, not a behavior regression — the tray still defaults on.Fix
On branch
claude/review-open-issues-prs-d4cjs6(commit6091615):setContextMenuargument, skips thenullclear, and takes the first that resolves to aVAR=BUILDER()assignment (→S5A).TRAY.destroy()and walking back to the;if(that opens its statement — lands correctly in both old and new shapes.patch_menu_bar_defaultnow distinguishes the upstream-defaults-map case from a genuine miss, warning loudly only if neither the legacy anchor normenuBarEnabled:!0is present.tests/tray-patches.bats(7 cases): builder resolution past thenulldecoy, injection placement + valid JS, idempotency, loud-fail on a missing destroy site, and the three menu-bar-default branches. Full suite 329/329.Verified end-to-end against the real
1.15962.0asar (patched output passesnode --check, fast-path lands before the destroy block, idempotent on re-run).Remaining
The whole point of the fast-path is StatusNotifier timing on Plasma, so runtime confirmation of the #515 race avoidance still needs a KDE Plasma host before merge. Flagging for on-device verification.
Written by Claude via Claude Code