Skip to content

fix(launcher): installed web apps launch through TronBrowser, not the raw engine - #87

Merged
ralyodio merged 1 commit into
mainfrom
worktree-fix-pwa-desktop-launch
Aug 30, 2026
Merged

fix(launcher): installed web apps launch through TronBrowser, not the raw engine#87
ralyodio merged 1 commit into
mainfrom
worktree-fix-pwa-desktop-launch

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

The bug

Installed web apps crash when launched from their desktop icon, but open fine
when installed from the address bar.

Why

Installing a web app writes a freedesktop shortcut, and the engine writes it
against itselfExec= is the Chromium binary plus --app-id=:

Exec=/app/chromium/chrome --user-data-dir=~/.tronbrowser --profile-directory=Default --app-id=<id>

That is the one launch path in TronBrowser that never comes through
launcher/tronbrowser. Clicking the icon starts a fresh browser with none of
what the launcher sets up — no --load-extension, no --class, and no GPU mode
from tron gpu safe|off. Two ways it presents:

  • Flatpak (the usual Ungoogled Chromium on Linux): the recorded path is the
    in-sandbox /app/... one, which does not exist on the host. Nothing can
    launch it.
  • System Ungoogled Chromium: it launches, but with the stock backend — so a
    machine kept usable by tron gpu safe gets, in its PWA windows only, exactly
    the GPU process those settings exist to avoid.

Installing from the address bar works because the app window is created by the
already-running browser, which the launcher had configured.

The fix

launcher/tron-pwa repoints those shortcuts at the launcher, keeping every
Chromium switch they carried — app id, profile directory, user data dir, and the
[Desktop Action] shortcuts-menu switches — so each icon still opens the app it
named in the profile it was installed in.

It hands the shortcut's own StartupWMClass back as --class: the launcher
stamps --class=TronBrowser on everything it starts, and a window whose class
does not match its StartupWMClass never binds to its taskbar entry.

The shim runs sync on every start, not once at install — the engine
rewrites these files whenever an app's manifest or icon changes, putting the
engine path straight back.

Scope

Only shortcuts whose --user-data-dir is a TronBrowser profile are touched; a
real Chrome's web apps come out byte-identical (pinned by a test). tron remove
reverts, so uninstalling does not strand every icon on a launcher that is about
to be deleted.

tron pwa [list]   show installed web apps and how their icons launch
tron pwa sync     repoint them at TronBrowser
tron pwa revert   hand them back to the engine

Tests

14 new cases in apps/desktop/test/pwa.test.ts, including one that runs the real
shim against a stub browser and asserts a broken shortcut is repaired on start.
Full desktop suite: 103 passing. Typecheck and lint clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SZXtxiVkXd7rFmvrMYV7Ut

… raw engine

Installing a web app writes a freedesktop shortcut, and the engine writes it
against ITSELF -- `Exec=` is the Chromium binary plus `--app-id=`. That is the
one launch path in TronBrowser that never comes through launcher/tronbrowser,
so an app started from its desktop icon gets none of what the launcher sets up:
no bundled extensions, no --class, and no GPU mode. Under the Flathub engine the
recorded path is the in-sandbox /app/... one, which does not exist on the host
at all, so the icon cannot launch anything.

That is why "install app" from the address bar works and the same app dies from
its icon: the address bar hands the request to a browser the launcher already
configured, while the icon starts a fresh one it never touched.

launcher/tron-pwa repoints those shortcuts at the launcher, keeping every
Chromium switch they carried -- the app id, the profile, the user data dir and
the shortcuts-menu switches -- so each icon still opens the app it named, in the
profile it was installed in. It hands back the shortcut's own StartupWMClass as
--class, because the launcher stamps --class=TronBrowser on everything it starts
and a window whose class does not match never binds to its taskbar entry.

The shim runs it on every start rather than once at install: the engine rewrites
these files whenever an app's manifest or icon changes, which puts the engine
path straight back. `tron pwa` is the manual handle and `tron pwa list` the
diagnostic; `tron remove` reverts, so uninstalling does not strand every icon on
a launcher that is about to be deleted.

Only shortcuts whose --user-data-dir is a TronBrowser profile are touched. A
real Chrome's web apps are left byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SZXtxiVkXd7rFmvrMYV7Ut
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

49 finding(s)

MEDIUM: 31 | LOW: 18

Severity Rule Location
MEDIUM js-open-redirect apps/desktop/extensions/ai-sidebar/install-helper.js:156
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:34
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:57
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:237
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:266
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:336
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/options.js:305
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:77
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:165
MEDIUM sh-remote-script-execution apps/desktop/launcher/tronbrowser:84
MEDIUM sh-remote-script-execution apps/desktop/launcher/tronbrowser:394
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:77
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:227
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:569
MEDIUM js-unescaped-html-sink apps/web/public/app.js:29
MEDIUM js-unescaped-html-sink apps/web/public/dns.js:54
MEDIUM sh-remote-script-execution apps/web/public/install.sh:138
MEDIUM sh-remote-script-execution apps/web/public/install.sh:143
MEDIUM sh-remote-script-execution apps/web/public/install.sh:242
MEDIUM sh-remote-script-execution apps/web/public/install.sh:260
MEDIUM sh-remote-script-execution apps/web/public/install.sh:820
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:96
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:168
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:80
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:92
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:103
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:65
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:70
MEDIUM sql-template-interpolation services/api/src/store/db.ts:116
MEDIUM js-dynamic-code-execution services/api/src/store/scanner.ts:44
MEDIUM sh-predictable-temp-path start.sh:25
LOW js-dynamic-code-execution packages/agent-runtime/src/analyze/form-script.test.ts:6
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:26
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:49
LOW js-dynamic-code-execution packages/browser-core/src/automation/extract-script.test.ts:6
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:35
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:47
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:70
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:89
LOW js-dynamic-code-execution packages/browser-core/src/automation/snapshot-script.test.ts:11
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:24
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:63
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:47
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:170
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:216
LOW secret-generic-api-key packages/storage/src/config.ts:51
LOW secret-generic-credential packages/storage/src/config.ts:51
LOW js-dynamic-code-execution services/api/src/store/scanner.test.ts:32
LOW js-hardcoded-crypto-key services/api/src/store/signing.test.ts:24

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 68d1894 into main Aug 30, 2026
8 checks passed
@ralyodio
ralyodio deleted the worktree-fix-pwa-desktop-launch branch August 30, 2026 14:30
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