Skip to content

feat(research): bake profile=research, port 5667 and a distinct bundle id into research builds - #1434

Merged
ErikBjare merged 8 commits into
ActivityWatch:masterfrom
TimeToBuildBob:bob/research-edition-profile-wiring
Sep 8, 2026
Merged

feat(research): bake profile=research, port 5667 and a distinct bundle id into research builds#1434
ErikBjare merged 8 commits into
ActivityWatch:masterfrom
TimeToBuildBob:bob/research-edition-profile-wiring

Conversation

@TimeToBuildBob

@TimeToBuildBob TimeToBuildBob commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Why

The Research Edition profile stack (#1399) is done, but the research build never consumed it: v0.14.0b5-research resolves profile=default, binds 5600, and shares activitywatch/ with a standard install. Erik promised Matthias (2026-09-04) that the research build runs in parallel with a personal install, so the recut needs isolation baked in.

The profile is build identity, not a launch argument. A login item passing --profile research would miss double-click, Spotlight, and the updater relaunch — each of those would silently start default and pollute the participant's standard install. Autostart phase 3 (--profile in login items) is therefore not a research-edition gate and stays a generic multi-profile feature.

What

New fail-closed patcher scripts/patch_research_edition_profile.py {qt|tauri} (same pattern as patch_research_edition_awqt.py), wired into all three build jobs for AW_RESEARCH_EDITION == 'true':

  1. Profile fallback → research. Every "no --profile, no AW_PROFILE" fallback in aw-qt, aw-server, aw-client, aw-server-rust and aw-tauri resolves to a new BUILD_PROFILE = "research" constant instead of DEFAULT_PROFILE. The launchers then set AW_PROFILE=research for their children, so dirs land in activitywatch-research/ and the lockfiles are aw-qt-research.lock / single_instance-research.lock.
    DEFAULT_PROFILE itself is deliberately not flipped: it means "the ordinary install" in suffix/dir/lockfile/export_profile logic (aw-qt export_profile pops the env for it; aw-tauri dirs.rs maps it to the bare root), so flipping it would collapse the isolation this PR exists to create.
  2. Port 5600 → 5667 in both servers, aw-client, aw-qt's tray/manager fallbacks, and aw-tauri's UserConfig::default, so a fresh research profile binds 5667 with no config file yet. (aw-tauri embeds aw-server-rust in-process and passes its own config port, so only the aw-tauri default matters there.)
  3. Distinct macOS bundle identity: CFBundleIdentifier net.activitywatch.ActivityWatch-research (Tauri identifier net.activitywatch.tauri-research), CFBundleName "ActivityWatch Research" — in aw.spec, build_app_tauri.sh, tauri.conf.json, notarize.sh. Dual-run becomes a real LaunchServices identity with its own login item and TCC grant instead of a shared slot; Dock/Spotlight show two names.

Every site is an exact, unique string match; a missing or ambiguous target aborts the build. 24 sites for the Qt bundles, 14 for Tauri; --check verifies without writing and currently passes against the pinned submodules.

Step ordering (why the new step is after make test)

The module test suites assert the ordinary defaults (resolve_profile(None) == "default", port 5600) and would fail on the patched tree. So the profile bake runs after Run tests/Run integration tests and before Package. Python modules are editable installs (poetry install), so PyInstaller collects the patched source at package time; the Rust binaries get an explicit rebuild step (make --directory=aw-server-rust aw-server in the Qt jobs, make --directory=aw-tauri build in the Tauri job — incremental cargo, aw-webui/dist is reused). The three existing research patch steps stay where they are; their targets aren't covered by unit assertions.

Not in this PR

  • Retagging v0.14.0b5-research or cutting b6-research — recut is Erik's call after merge.
  • Upstreaming BUILD_PROFILE into the submodules themselves. The patcher inserts the constant at release time; if a submodule later grows the constant natively, the patcher fails closed and its table gets trimmed.
  • aw-qt reads config-<profile>.toml for the rust server's port while aw-server-rust writes bare config.toml in isolated roots — a pre-existing aw-qt lookup mismatch. The baked 5667 fallback covers the research build; the mismatch is worth an aw-qt fix separately.

Verification

  • python3 scripts/patch_research_edition_profile.py qt --check / tauri --check: all sites present and unique on the current pins.
  • Applied to a copy of the tree: the patched aw-qt/aw-server/aw-client profile modules return research for resolve_profile(None, False), testing for --testing, keep DEFAULT_PROFILE == "default", and export_profile sets AW_PROFILE=research; aw.spec still parses; tauri.conf.json still valid JSON; shell scripts pass bash -n.
  • scripts/tests/test_patch_research_edition_profile.py covers apply/check/fail-closed/no-double-apply plus a live check against the pinned submodules.
  • Smoke test on the next research build: profile=research, dashboard on 5667, dirs under activitywatch-research/, CFBundleIdentifier ends in -research, and a standard install keeps running alongside.

Refs: #1399 (profile stack), ErikBjare/bob#1108 (Research Edition b5 tracking).

Companion: ActivityWatch/aw-watcher-window#143 merged at abd69a6. This PR now pins aw-watcher-window there (2868946) so the recut's patch→test path skips the two #138 guards under AW_RESEARCH_EDITION=true — the failure that killed run 34155142876.

…e id into research builds

The Research Edition profile stack (ActivityWatch#1399) shipped, but the research build
never consumed it: v0.14.0b5-research resolves profile=default, binds 5600
and shares activitywatch/ with a standard install. The profile is build
identity, not a launch argument: a login-item --profile would miss
double-click, Spotlight and the updater relaunch.

New fail-closed patcher scripts/patch_research_edition_profile.py {qt|tauri}
(same pattern as patch_research_edition_awqt.py), wired into all three
build jobs for AW_RESEARCH_EDITION == 'true':

- every no-flag/no-env profile fallback in aw-qt, aw-server, aw-client,
  aw-server-rust and aw-tauri resolves to BUILD_PROFILE = "research";
  DEFAULT_PROFILE itself is left alone because it means the ordinary
  install in suffix/dir/lockfile/export_profile logic
- built-in port 5600 -> 5667 in both servers, aw-client, aw-qt's tray and
  manager fallbacks, and aw-tauri's UserConfig::default
- CFBundleIdentifier net.activitywatch.ActivityWatch-research (Tauri
  identifier net.activitywatch.tauri-research) and CFBundleName
  "ActivityWatch Research" in aw.spec, build_app_tauri.sh,
  tauri.conf.json and notarize.sh

The bake runs after the module test suites (which assert the ordinary
defaults) and before Package; Python modules are editable installs, the
Rust binaries get an explicit rebuild step.

Git-Session-Id: cb3d
@greptile-apps

greptile-apps Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a fail-closed build-time patcher that gives Research Edition builds isolated profile, port, bundle, installer, package, and autostart identities across supported platforms.

  • Adds dedicated patch-table and fixture checks on pull requests and pushes.
  • Adds Linux research build legs that compile and package the patched Qt and Tauri trees.
  • Separates macOS bundle filenames, Windows installer identities, Linux package paths, and first-run autostart entries.
  • Updates the window watcher pin for Research Edition compatibility.

Confidence Score: 5/5

The PR appears safe to merge; no actionable new defects or outstanding previous findings remain.

The prior bundle-filename collision and CI-coverage findings are fully addressed, and the subsequent changes consistently separate installer and autostart identities while adding patched-tree build validation.

Important Files Changed

Filename Overview
.github/workflows/release.yml Adds patcher validation and patched-tree research build legs with distinct artifact naming.
scripts/patch_research_edition_profile.py Implements fail-closed source transformations for research profile, port, packaging, and autostart isolation.
scripts/tests/test_patch_research_edition_profile.py Covers patch application, drift detection, ordinary defaults, and cross-platform identity separation.
Makefile Parameterizes macOS application and DMG filenames so standard and research bundles can coexist.
aw-watcher-window Advances the watcher pin to a revision compatible with Research Edition builds.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Standard source tree] --> B[Run ordinary tests]
  B --> C{Research build?}
  C -- No --> D[Package standard edition]
  C -- Yes, Qt --> E[Apply Qt research patches]
  C -- Yes, Tauri --> F[Apply Tauri research patches]
  E --> G[Rebuild Rust server]
  F --> H[Rebuild Tauri application]
  G --> I[Package isolated Research Edition]
  H --> I
  I --> J[research profile and port 5667]
  I --> K[distinct bundle or installer identity]
  I --> L[distinct autostart identity]
Loading

Reviews (3): Last reviewed commit: "ci: research-edition build leg on PR/bra..." | Re-trigger Greptile

Comment thread scripts/patch_research_edition_profile.py
Comment thread scripts/tests/test_patch_research_edition_profile.py
…in CI

The research macOS bundle still installed as ActivityWatch.app, so dragging
it into Applications replaced a standard install. Bake the on-disk stem
(ActivityWatch-Research.app / .dmg) through aw.spec, APP_NAME, Makefile,
and notarize.sh, and parameterize the dmg job on APP_BUNDLE.

Also run the fail-closed patcher tests on every release/PR job before
patching, so table drift fails at PR time instead of tag time.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

Watcher ActivityWatch#143 squash-merged at abd69a6. The two ActivityWatch#138 guards now skip when
AW_RESEARCH_EDITION=true, which is what killed v0.14.0b5-research run
34155142876. Pin folded into this PR so the recut does not need a
separate bump round-trip.

Git-Session-Id: pm-aw143
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Watcher #143 squash-merged at abd69a6. Folded the pin into this PR (a101165abd69a6, 2868946) so the recut's patch→test path picks up the AW_RESEARCH_EDITION guard skips.

Remaining recut landing list here: 5.2 Windows install identity, 5.3 Linux package identity, 5.4 autostart labels, 6.2 research-edition PR-CI build leg. Will ping for review when those land — not asking for merge yet.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Build Qt artifacts (macos-15-intel) failed in aw-watcher-afk poetry install (shared-venv pip FileNotFoundError on aw_core-0.5.18.dist-info while uninstalling typing-extensions). Research patcher steps were skipped on this PR job.

Same job was green on 3689ad58 and 7609fd39; macos-latest Qt is green in this run. I cannot gh run rerun --failed (403 admin rights). Next recut commit will retrigger; not empty-commit rebuilding the matrix.

Windows Qt/Tauri still running on https://github.com/ActivityWatch/activitywatch/actions/runs/34161729287

…ntity

Both Inno Setup scripts describe a single product each. A research build made
from them would share the standard AppId, so Windows treats it as the *same*
product: the research setup upgrades over an existing install and its
uninstaller removes both. That defeats the dual-run the research edition
exists for.

Patch, fail-closed, at release time:

- activitywatch-setup.iss (qt): AppId -> 32024B9B-..., MyAppName ->
  "ActivityWatch Research", OutputBaseFilename -> activitywatch-research-setup
- aw-tauri.iss (tauri): AppId -> 70E2D4AB-..., MyAppName ->
  "ActivityWatch Research (Tauri)", DefaultDirName ->
  ActivityWatch-Research-Tauri, OutputBaseFilename ->
  activitywatch-research-tauri-setup
- tauri.conf.json: pin bundle.windows.wix.upgradeCode

AppName, DefaultDirName (qt), the Start-Menu / desktop / {userstartup}
shortcuts and UninstallDisplayName all derive from `#define MyAppName`, so one
patched token cascades to every user-visible identity. The tests assert that
cascade against the *real* .iss files rather than a synthetic fixture, so
upstream hardcoding a shortcut name fails the research build instead of
silently shipping a colliding Start-Menu entry.

WixConfig exposes no product-code field (Tauri generates one per build); the
upgrade code is what defines the MSI product family, so that is the one pinned.
It was already distinct via the patched identifier, but only as a side effect
of an unrelated string.

Note both .iss files ship OutputBaseFilename=activitywatch-setup on master, so
the qt and tauri setups already overwrite each other in dist/. The research
names are distinct from each other as well as from standard.

Refs ActivityWatch#1434

Git-Session-Id: 26d3
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Same macos-15-intel Qt flake reproduced on f96b8d3 (run 34164551339, job 101872903593). aw-watcher-afk poetry install again: shared-venv pip FileNotFoundError on importlib_metadata-6.8.0.dist-info while uninstalling idna. Research patcher still skipped on this PR job.

macos-latest Qt is green in this run. gh run rerun --failed still 403 (no admin). Not empty-commit rebuilding the matrix; 5.3 Linux package identity is the next recut push and will retrigger.

Windows Qt/Tauri and macos-15-intel Tauri still running: https://github.com/ActivityWatch/activitywatch/actions/runs/34164551339

The research edition already had its own profile, port, macOS bundle and
Windows installer identity. Linux was still a single product: the deb is
`Package: activitywatch` installing `/opt/activitywatch`, and it drops
`aw-qt.desktop` into both /etc/xdg/autostart and /usr/share/applications.
Installing a research deb next to a standard one therefore replaces it --
dpkg treats a same-named package as an upgrade -- and its autostart entry
overwrites the standard one.

Bake a distinct Linux identity at release time: package
`activitywatch-research`, `/opt/activitywatch-research`,
`activitywatch-research.desktop` in both shared namespaces, a rebranded
`Name=`, and a matching AppImage icon id / desktop-entry filename.

Also split what aw-qt registers for itself on first run (config
`autostart_on_first_run`): the Linux autostart filename, the macOS
LaunchAgent label, and APP_NAME (which the Windows Run value and Startup
shortcut derive from). Installer identity does not cover these -- without
them the two editions still overwrite each other's autostart.

The Linux autostart patch targets `_linux_desktop_path()` rather than
`DESKTOP_FILENAME`, which also names the shipped resource the build reads
out of the bundle; flipping the constant would send `_bundled_desktop_file()`
looking for a file that is not there.

Tauri's Linux bundles come from Tauri's own bundler, keyed on `productName`,
which is coupled to the cargo binary name -- left for a change that can be
verified against a real Tauri build.

Tests patch the real packaging scripts, not a synthetic fixture, so upstream
adding another /opt/activitywatch reference or a fourth desktop-entry copy
fails the research build instead of shipping a collision.

Git-Session-Id: e26e
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

macos-15-intel Qt flake on f96b8d3 (run 34164551339) is the same shared-venv poetry install race as before — gh run rerun --failed is still 403. Did not empty-commit.

Pushed 99ae754 (Linux package identity + Qt first-run autostart split) so the Release matrix starts a fresh run instead: https://github.com/ActivityWatch/activitywatch/actions/runs/34166954550

Research patcher steps were not involved in the flake (AW_RESEARCH_EDITION=false on PR jobs).

Adds a dedicated `research-edition-checks` job that runs on every PR and
push to master.  It checks out with submodules and verifies that every
patch target in the Qt and Tauri tables still exists exactly once in the
real source files (`--check` mode).  This is the fail-closed guard that
previously only fired when Erik pushed the annotated release tag.

The job is stdlib-only (no poetry/build step) so it completes in ~30s.
It also runs the fixture-based profile patcher unit tests.

Closes task step 6.2 of the research-edition-profile-wiring checklist.

Git-Session-Id: f8ff
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Step 6.2 landed (706ba75): added a research-edition-checks CI job that runs on every PR and push to master.

The job checks out with submodules and runs the patcher in --check mode against the real source files for both Qt and Tauri targets. This is the fail-closed guard that previously only fired when Erik pushed the annotated release tag — it now fires at review time so a drifted submodule pin fails before anyone spends ~30 min on a full tag build.

The job is stdlib-only (no poetry/build step) so it completes in ~30s. It also runs the fixture-based profile patcher unit tests.

Remaining open: step 5.4 — Tauri autostart identity (tauri_plugin_autostart still uses standard identifier-derived names).

…tinct app_name

tauri_plugin_autostart uses productName ("aw-tauri") as the OS autostart entry
name by default. Standard and research Tauri builds sharing that name means
enabling autostart in one edition overwrites the other's entry on Windows
(registry Run key) and Linux (~/.config/autostart/).

Add AUTOSTART_PATCHES_TAURI that replaces tauri_plugin_autostart::init() with
a Builder::new() call. After the profile patches have also run, BUILD_PROFILE
is defined in profile.rs. When BUILD_PROFILE != DEFAULT_PROFILE the Builder
sets app_name("aw-tauri-research"), giving the research binary its own entry
name. MacosLauncher selection (AppleScript vs LaunchAgent) is preserved.

Tauri's Linux bundler derives package/desktop names from productName, so a
full Linux package identity split needs a separate investigation; the collision
that is fixed here is the runtime autostart entry written by the plugin itself.

Add test asserting the patcher inserts BUILD_PROFILE and that the lib.rs patch
produces Builder code with the conditional app_name override.

Git-Session-Id: 49c9
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Correction on 6.2: 706ba75 is not the research-edition build leg.

That job (Research Edition — patch table smoke test) runs patch_research_edition_profile.py {qt,tauri} --check plus the unit tests against the unpatched tree. It catches patch-target drift. It does not apply the patches or compile/package a research build.

5.2–5.4 are in:

  • 5.2 Windows identity f96b8d3
  • 5.3 Linux package identity 99ae754
  • 5.4 Qt autostart 99ae754; Tauri AUTOSTART_PATCHES_TAURI 82dc91c

Original 6.2 (patched-tree PR-CI build) is still coming before a ready-ping. Not asking for merge yet.

The --check smoke test (706ba75) only catches patch-target drift on the
unpatched tree. A patched-tree compile/test/package failure -- the class
that killed the first v0.14.0b5-research run -- still only surfaced at
tag time. Add a `research` matrix dimension with one Linux include row
per build job (Qt on ubuntu-22.04, Tauri on ubuntu-24.04) that sets
AW_RESEARCH_EDITION=true, so every PR and branch push applies the full
research patch set and builds, tests and packages the patched tree.

On tag pushes and workflow dispatches the include row's `research` value
resolves to false, so it merges into the existing Linux row instead of
adding a duplicate leg: research tags already build the research edition
in every row, and standard tags must not upload research artifacts to the
standard release. Artifact names carry a -research suffix so the leg
cannot collide with the standard row on the same runner.

Git-Session-Id: 0283
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

6.2 pushed (5d5faa8): research-edition build leg on PR/branch CI.

  • research matrix dimension on build-qt and build-tauri, one Linux include row each (Qt ubuntu-22.04, Tauri ubuntu-24.04) that forces AW_RESEARCH_EDITION=true. The existing research steps (config/awqt patch, export sanitizer, category preset, profile patcher, aw-server-rust / aw-tauri rebuild, -research packaging) run unchanged on that row, so it exercises the exact release path on the patched tree.
  • On tag pushes and dispatches the row's research value resolves to false and merges into the standard Linux row: no duplicate leg on research tags, no research artifacts attached to a standard release. Artifact names carry a -research suffix on the PR leg.
  • Side effect: every existing build check name gains a trailing , false from the new dimension. If master pins check names in branch protection they'll need a one-time rename.

Run 34183065594 → 34183838348: both (…, true) legs are present, Qt in progress, Tauri queued. Ready-ping follows once both are green.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Ready for review. Both research build legs are green on run 34183838348:

  • Build Qt artifacts (ubuntu-22.04, …, true) — patched-config tests, profile-identity patch, aw-server-rust rebuild, zip/AppImage/deb → builds-ubuntu-22.04-py3.9-research
  • Build Tauri artifacts (ubuntu-24.04, …, true) — 23-site profile patch, make --directory=aw-tauri build with the replaced autostart block and inserted BUILD_PROFILE, Linux bundles + updater artifacts → builds-tauri-ubuntu-24.04-py3.9-research

Every other finished job is green; two standard rows (Qt macos-15-intel, Tauri windows) were still running at 04:05Z. Recut landing list items 1–3 are all in on this branch (2868946, f96b8d3/99ae754/82dc91c, 5d5faa8). After merge: submodule pin(s) if any, then re-push the annotated v0.14.0b5-research at the final SHA; the post-recut smoke test must expect profile=research, port 5667, activitywatch-research/ dirs.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Full matrix on run 34183838348 is now green, including the two standard rows still running at the ready-ping (Qt macos-15-intel, Tauri windows). No remaining CI.

Greptile re-triggered on 5d5faa8 (last review was 7609fd3). Waiting for human review/merge; recut tag stays Erik's after merge.

@ErikBjare
ErikBjare merged commit e68707f into ActivityWatch:master Sep 8, 2026
22 checks passed
@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob

aw-qt reads config-.toml for the rust server's port while aw-server-rust writes bare config.toml in isolated roots — a pre-existing aw-qt lookup mismatch. The baked 5667 fallback covers the research build; the mismatch is worth an aw-qt fix separately.

Fix that, aw-qt should use isolated roots too. Also check aw-server which used to use config sections for testing mode in same file as prod conf.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Fixed.

Submodule pins in this repo after those merge. Recut tag still yours.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Follow-ups are merge-ready: ActivityWatch/aw-qt#132 and ActivityWatch/aw-server#170 (CI green; I cannot self-merge — pull-only). Research patcher targets still match those heads. Pins after those merge. Recut stays on #1436; baked 5667 still covers a fresh research profile.

ErikBjare pushed a commit to ActivityWatch/aw-qt that referenced this pull request Sep 8, 2026
…e>.toml (#132)

* fix(config): read isolated-root config.toml instead of config-<profile>.toml

aw-server-rust writes bare config.toml under activitywatch-<profile>/.
aw-qt still looked for config-<profile>.toml (and [server-<profile>]) in
whatever dir AW_PROFILE currently pointed at, so a research instance
never saw the rust server's port.

Lookup now sets AW_PROFILE for the target profile, reads config.toml /
[server] in the isolated root, and only uses the legacy suffixed names
when testing still lives on the shared activitywatch/ root.

Refs: ActivityWatch/activitywatch#1434

* fix(config): look up pre-isolation profile files in the shared root

Isolated-root lookup set AW_PROFILE first, so the config-<profile>.toml
/ [server-<profile>] / [aw-qt-<profile>] fallbacks ran inside
activitywatch-<profile>/ instead of the pre-isolation activitywatch/
root. Existing named profiles then lost their port and module list.

Search the shared root for those suffixed files/sections after the
isolated bare config misses.
ErikBjare added a commit that referenced this pull request Sep 8, 2026
The Research Edition patcher (#1434) rewrites OutputBaseFilename in both
.iss files to activitywatch-research[-tauri]-setup, but package-all.sh
still moved the hardcoded standard name — failing both Windows jobs on
the v0.14.0b5-research tag build (mv: cannot stat). Glob the single
produced setup exe instead, failing loudly if the count is ever not 1.
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.

2 participants