Skip to content

Fix multi-second first-click delay after boot (startup optimizations, v0.3.0.0)#67

Open
martinperreault wants to merge 3 commits into
PikeNote:masterfrom
martinperreault:startup-optimizations
Open

Fix multi-second first-click delay after boot (startup optimizations, v0.3.0.0)#67
martinperreault wants to merge 3 commits into
PikeNote:masterfrom
martinperreault:startup-optimizations

Conversation

@martinperreault

Copy link
Copy Markdown

Summary

Fixes the multi-second delay on the first click of a pinned taskbar group after a reboot (subsequent clicks were always instant). Root cause: that first click has to cold-start the tray-resident single-instance background host; everything after just messages the warm instance.

Four fixes attack the cold start (full analysis in FINDINGS.md, plan + verification in OPTIMIZATION_PLAN.md / PLAN_REVIEW.md):

  1. Repair Multicore JIT on the hot pathProfileOptimization.StartProfile was already called in frmMain, but SetProfileRoot only ever ran in the editor, making it a silent no-op for the background client. Now set in Program.Main, with a startup profile added. (Easy to confirm on any install: %LocalAppData%\Jack Schierbeck\taskbar-groups\JITComp\ never contained background-client profiles before this change.)
  2. Lazy group loading — startup previously deserialized every group's XML and decoded every icon PNG before the first popup could appear. Startup now only indexes group folders; a group loads on demand at click time, and all groups pre-warm on a background thread after tray init.
  3. Defer jump-list construction (WindowsAPICodePack + shell COM) until after the popup is shown. The setGroupContextMenu registration path is preserved.
  4. New "Start at login (instant first click)" toggle in the editor (default on, HKCU Run key, disabled in portable mode, applied immediately). Launching the background exe with no arguments already starts it tray-resident without a popup, so this needed no new hot-path code. This is the decisive fix: the host is resident before the user's first click.

Bug fixes

  • Editor crash when upgrading while the background host is running (latent bug, found during rollout testing): closeBackgroundApp spawns a messenger copy of the background exe to deliver the exit signal but only waited on the resident process — the messenger also locks the exe file, and Process.Kill() is async, so the immediate File.WriteAllBytes in Paths.setupBackgroundApplication raced the lock and threw an unhandled IOException from the static initializer. Now waits on all instances and retries the write; on persistent failure it keeps the previous helper and informs the user instead of crashing. With launch-at-login, a resident host during upgrades is the normal case.
  • Portable-mode toggle wrote portableMode=true even when disabling portable mode (previously self-corrected only on next launch).

Compatibility

Existing groups, pinned shortcuts, AppUserModelIDs, config layout, Settings.xml schema (new element is optional with a default), and the pinned-.lnk argument contract are unchanged. Upgrading is: run the new editor once (existing MD5 self-update mechanism replaces the background exe).

Build

build.ps1 added: reproducible Release build (offline NuGet layout restore from the checked-in lib\ DLLs, COM interop generation, correct build order — background client before editor, since the editor embeds it). Version bumped to 0.3.0.0.

Testing

  • Verified on a live Windows 11 install with 3 pinned groups: helper replaced in place, login entry registered, host resident immediately, popups open through the same code path a taskbar click uses, pins/groups untouched.
  • No-arg launch → tray-resident, no popup; <GroupName> arg → popup via single-instance handoff; exitApplicationModeReserved → clean exit; JIT profiles now written.

🤖 Generated with Claude Code

martinperreault and others added 3 commits July 2, 2026 14:02
… v0.3.0.0)

The first click on a pinned group after a reboot had to cold-start the
tray-resident background host (~5s); every later click was instant. This
release attacks that cold start on four fronts:

- Fix Multicore JIT in the background client: SetProfileRoot was only
  ever called in the editor, making the existing StartProfile call a
  silent no-op on the hot path. Also profile the startup path itself.
- Lazy group loading: startup now only indexes group folders; XML
  deserialization + icon decoding happen on demand at click time, with
  all groups pre-warmed on a background thread after tray init.
- Defer jump-list construction (WindowsAPICodePack + shell COM) until
  after the popup is shown; the setGroupContextMenu registration path
  is preserved.
- New "Start at login (instant first click)" toggle in the editor
  (default on, HKCU Run key, disabled in portable mode): the host is
  already resident before the first click, hiding any remaining cold
  start entirely. Launching the exe with no args already starts it
  resident, so no new argument was needed.

Also fixes portable-mode toggle writing portableMode=true even when
disabling (previously self-corrected only on next launch).

Docs: OPTIMIZATION_PLAN.md (final plan), PLAN_REVIEW.md (independent
verification), CHANGELOG.md, build.ps1 (reproducible Release build).

Existing groups, pins, AppUserModelIDs, config layout, and the pinned
.lnk command-line contract are unchanged; upgrading is running the new
editor once (existing MD5 self-update mechanism).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
closeBackgroundApp launched a messenger copy of the background exe to
deliver the exit signal but only waited on the resident process; the
messenger also holds a lock on the same exe file, so the immediate
WriteAllBytes in setupBackgroundApplication raced it and threw an
unhandled IOException from inside the Paths static initializer,
killing the editor mid-upgrade (found live during the v0.3.0.0
rollout; a resident host is the normal case now that launch-at-login
exists).

- closeBackgroundApp: wait on the messenger process and on every
  remaining instance, including after Kill() (which is async).
- setupBackgroundApplication: retry the exe write briefly on
  IOException; on persistent failure keep the previous helper and
  inform the user instead of crashing (mismatch retries next launch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Condensed record of the investigation: root causes with proof, the
cost-attribution correction, the latent upgrade-race bug found during
rollout, verification performed, and the compatibility invariants any
future change must preserve.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant