Skip to content

tray.sh in-place fast-path silently broken on 1.13576+ (yukonSilver rebuild refactor) → #515 duplicate-icon race re-arms #750

Description

@aaddrick

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 !!varvar!==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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: highImportant, should be addressed soonpriority: mediumShould be addressed when possibleregressionPreviously working, now brokentriage: investigatedIssue has been triaged and investigated

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions