0051 — The installed app's launch surface: a real GUI entry point, prefix shims, and a menu shortcut
- Status: accepted
- Date: 2026-07-20
- Deciders: bioedca
- PRD anchor: §7.8 (shell / project open), §4.1 (installers), §9 M9 (packaging & docs)
- Milestone: M9
Context and problem statement
ADR-0049 deliberately makes the installer leave the user's machine alone: initialize_conda: false,
initialize_by_default: false, register_python: false and menu_packages: [] (globally and under
extra_envs.tether). That is correct — Tether is a desktop application, not a conda distribution, and
the bootstrap conda in base must never initialise someone's shell.
The unintended consequence is that an installed Tether cannot be started at all:
pyproject.tomldeclared exactly one entry point,tether = "tether.cli:main"under[project.scripts]. The console script is installed into<prefix>/envs/tether, which is never added toPATH, sotether --version— the verification step an install guide must give — fails in a fresh terminal.- There was no GUI entry point at all. The only callable that opens a window is
tether.gui.shell.launch, whose own docstring describes it as the "computer-use live-smoke entry": it fabricates six synthetic traces and a demo project. Wiring a shortcut to it would ship a demo, not the application. menu_packages: []means no shortcut is created, so there is no icon either.
Every user-facing M9 deliverable is blocked on this: the install guide, the CLI reference, the README quickstart and the GUI tour all describe invocations that do not work for someone who ran the installer.
Decision drivers
- A scientist who double-clicks a signed installer must end up with something clickable.
- The documented verification step (
tether --version) must actually succeed. - ADR-0049's isolation invariants must survive: no re-solve, no merging the two envs, no shell init.
- A launcher must never open the synthetic-data smoke helper.
- Whatever is written must be recoverable if it goes wrong on a user's machine.
Considered options
- Document absolute per-OS paths and change nothing. Zero install-time risk, but the README quickstart, install verification and every CLI example stay false for installer users, and the product ships with no icon.
menuinstshortcut viamenu_packages. The constructor-native route — butmenuinstattaches shortcuts to a conda package, andtetheris an offline-pip-installed wheel (ADR-0049), so it can own no menuinst entry. Repackaging the wheel as a conda package to gain a shortcut would mean a new build/publish path for every release.- Have the installer edit
PATH. Makestetherwork in a fresh shell with no user action — but on Windowssetxsilently truncatesPATHbeyond 1024 characters, and on Unix it means rewriting a user's shell rc. Both damage state the installer does not own. - A real GUI entry point + prefix-local shims + a directly-created shortcut (chosen).
Decision outcome
Option 4. Three parts:
- A real GUI entry point.
tether.gui.app:mainbuildsTetherShellagainst an actual store: the.tethernamed on the command line, or none, in which case the shell opens empty and the curator uses the existing&File -> &Open project…. It is registered astether-guiunder[project.gui-scripts]— notscripts— so Windows produces a console-less launcher and a shortcut does not flash a terminal.create_shellis split frommainso the startup path is testable without entering the Qt event loop.python -m tether.guiis the equivalent module spelling. - Prefix-local shims.
post_installwrites<prefix>/bin/tetherand<prefix>/bin/tether-gui(.baton Windows) forwarding intoenvs/tether. One stable, documented directory means the install guide can name a single path to add toPATH. - Direct menu integration. A Start Menu
.lnkon Windows (created withWScript.Shell, sincemenuinstis unavailable per option 2) and a~/.local/share/applications/tether.desktopentry on Linux. macOS.pkginstalls get the shims only; a proper.appbundle is separate work.
A fourth change falls out of the first three and is not optional. ADR-0049
wires TETHER_SIDECAR_PYTHON through a conda activate.d hook, which runs only on
activation — so every launch path added here (shortcut, shim, .desktop) would start an app
whose idealization is broken, on an otherwise correct install. resolve_sidecar_python therefore
gains a third and final step after the argument and the environment variable: the installer's
sibling envs/sidecar, derived from sys.prefix. This is the "prefix-relative app-side default"
ADR-0049 already named as the more robust
follow-up. It resolves relative to the running interpreter rather than hard-coding a path, and a
development checkout — where no sibling exists — falls through to the same actionable error as before.
The installer still does not edit PATH. Adding <prefix>/bin stays a documented one-line step.
This is the deliberate trade in option 3: a user who skips the step has a working icon and a documented
absolute path, whereas a truncated PATH is not recoverable by documentation.
Consequences
- Good. The application is launchable from a menu on Windows and Linux;
tether --versionworks once the documented directory is onPATH; the install guide, CLI reference and README quickstart become writable against something real; no launch path reaches the synthetic-data helper. - Good. No change to either conda lock, so pin-and-hold and the two-stack isolation are untouched.
menu_packages: []stays empty, as does the no-shell-init posture. - Bad / accepted.
tetherin a fresh shell needs one documented user action. macOS has no icon yet. Both shell/batch launcher paths execute only on a real install, so they are verified by the packaging install-smoke and by a maintainer running the actual installers — not by the unit matrix. - Bad / accepted. The Windows shortcut is created imperatively rather than declaratively, so it is not removed by an uninstall that only unlinks conda packages.
More information
- Supersedes the "a GUI menu shortcut is a deferred follow-up" note in ADR-0049; the stale comments
in
packaging/construct.yamlare corrected in the same change. tests/test_gui_app.pyasserts the entry point is declared undergui-scripts, that it is nottether.gui.shell:launch, that a missing project path leaves a usable empty shell, and that--versionanswers without importing PySide6.