Skip to content

App-mode window has no AUMID — cldctrl, PhysioMetrics and plain Chrome all collapse into one taskbar button #17

Description

@RyanSeanPhillips

Symptom

Launching the dashboard in app mode (--app) does not give cldctrl its own taskbar button or icon. cldctrl, another local app-mode tool (PhysioMetrics), and the user's ordinary Chrome browsing window all collapse into a single Chrome taskbar button with the Chrome icon — so you can't click between them as distinct apps.

Measured evidence (not a guess)

Enumerated top-level windows of the running chromium processes and read PKEY_AppUserModel_ID (fmtid 9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3, pid 5) via SHGetPropertyStoreForWindow:

chrome  "(5 live) App Analytics · Usage Map"  -> AUMID unset (inherits process/exe default)
chrome  "PhysioMetrics"                       -> AUMID unset (inherits process/exe default)
chrome  "⌃ CLD CTRL"                          -> AUMID unset (inherits process/exe default)

Every window falls back to chrome.exe's identity → Windows groups them. Correct behaviour, wrong identity.

Root cause

packages/cli/src/…/app-launch:

const args = [`--app=${target}`, '--new-window'];
if (isolated) args.push(`--user-data-dir=${dir}`);

--app=URL + --user-data-dir is not enough. --app only removes browser chrome; --user-data-dir only isolates the profile/process tree. Neither creates a Windows app identity. Chromium mints a per-app AUMID only for installed web apps — not ad-hoc --app=URL windows. There is no Chromium switch that sets an arbitrary AUMID (--app-id selects an already installed app; it is not an AUMID setter).

This is presumably why the CLI already carries --shared-profile and a "chrome (default; better taskbar icon)" note — same wall, worked around rather than fixed.

Options (reviewed with a second agent)

  1. Installed PWA + --app-id — Chromium assigns a real AUMID/icon/taskbar button. --app-id=<id> is a real, current Chromium switch; the ID derives from the manifest ID (double SHA-256 + extension-style a–p encoding). Don't reproduce that algorithm — read the ID from the shortcut Chromium generates at install, or chrome://web-app-internals. Caveats: there is no supported way to install a PWA silently into a consumer profile (WebAppInstallForceList is enterprise policy — mandatory, un-uninstallable, registry-based; inappropriate here), and a Chrome-installed app is not visible to Edge, so "Chrome preferred, Edge fallback" becomes install state you must persist (browser + user-data-dir + profile-directory + app id).

    Relevant to cldctrl specifically: the dashboard serves on a fixed port (2533 default) → stable origin, which makes this path far more viable for cldctrl than for an app on a dynamic port (a changing port changes the origin and therefore the PWA identity; an explicit manifest id cannot rescue that). Note the bootstrap caveat still applies: a pinned PWA shortcut relaunches the browser, it does not start the cldctrl server.

  2. Set the AUMID on the window yourself — SHGetPropertyStoreForWindow + IPropertyStore::SetValue(PKEY_AppUserModel_ID). Two corrections worth recording: per-window AUMIDs are officially supported (the "must set before the window is shown" rule applies to the process-level SetCurrentProcessExplicitAppUserModelID, and a window-level ID overrides the process one), and Commit() is not needed for this property store — SetValue applies immediately. Practical notes: don't match the HWND by title — hook window create/show (SetWinEventHook), filter by PID/process tree + the Chrome_WidgetWin_1 frame class; also set RelaunchCommand/RelaunchIconResource and put the same AUMID on the shortcut. Still fragile: you're mutating a window Chrome owns, across browser updates.

  3. Native shell (Tauri / WebView2) — owns its own top-level HWND and can set its process AUMID before any UI. The durable answer, but a much bigger change.

Suggested

For cldctrl the fixed port makes (1) genuinely attractive; (2) is a reasonable bridge behind a flag. Filed so it's tracked — the same root cause is tracked on the PhysioMetrics side too.

Reported from a PhysioMetrics session at the operator's request.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions