Skip to content

Commit 4884ea3

Browse files
Prepare MSIX Store packaging
* Prepare MSIX Store packaging * Respect MSIX startup task state
1 parent 95d8246 commit 4884ea3

18 files changed

Lines changed: 797 additions & 27 deletions

.github/workflows/msix-preview.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: MSIX Preview Build
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/**"
7+
- "AGENTS.md"
8+
- "CONTRIBUTING.md"
9+
- "README.md"
10+
- "assets/**"
11+
- "docs/**"
12+
- "packaging/msix/**"
13+
- "packaging/windows/**"
14+
- "pyproject.toml"
15+
- "scripts/**"
16+
- "src/**"
17+
- "tests/**"
18+
workflow_dispatch:
19+
20+
env:
21+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
22+
23+
concurrency:
24+
group: msix-preview-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
25+
cancel-in-progress: true
26+
27+
permissions:
28+
contents: read
29+
30+
jobs:
31+
build-msix-preflight:
32+
runs-on: windows-latest
33+
steps:
34+
- name: Check out repository
35+
uses: actions/checkout@v5
36+
37+
- name: Set up Python 3.13
38+
uses: actions/setup-python@v6
39+
with:
40+
python-version: "3.13"
41+
cache: "pip"
42+
cache-dependency-path: "pyproject.toml"
43+
44+
- name: Install Python dependencies
45+
shell: powershell
46+
run: |
47+
python -m pip install --upgrade pip
48+
python -m pip install -e .[build,msix]
49+
50+
- name: Generate assets
51+
shell: powershell
52+
run: python scripts/generate_assets.py
53+
54+
- name: Build unsigned MSIX preflight package
55+
shell: powershell
56+
run: |
57+
powershell -ExecutionPolicy Bypass -File packaging\msix\build.ps1 `
58+
-IdentityName "Trayffeine.Preview" `
59+
-Publisher "CN=Trayffeine Preview" `
60+
-PublisherDisplayName "Trayffeine Preview" `
61+
-Clean
62+
63+
- name: Verify repeatable MSIX build cleanup
64+
shell: powershell
65+
run: |
66+
$stagingSentinel = "dist\msix\staging\__repeatability_sentinel.tmp"
67+
$verificationSentinel = "dist\msix\verification\__repeatability_sentinel.tmp"
68+
New-Item -ItemType File -Force -Path $stagingSentinel, $verificationSentinel | Out-Null
69+
70+
powershell -ExecutionPolicy Bypass -File packaging\msix\build.ps1 `
71+
-IdentityName "Trayffeine.Preview" `
72+
-Publisher "CN=Trayffeine Preview" `
73+
-PublisherDisplayName "Trayffeine Preview"
74+
75+
if ((Test-Path $stagingSentinel) -or (Test-Path $verificationSentinel)) {
76+
throw "The repeated MSIX build retained stale staging or verification files."
77+
}
78+
79+
- name: Upload MSIX preflight artifact
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: trayffeine-msix-preflight-${{ github.event.pull_request.number || github.run_number }}
83+
path: dist/msix/*.msix
84+
if-no-files-found: error

AGENTS.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Current product behavior:
2929
- persistent restore of infinite mode, while timed sessions always restart inactive
3030
- first launch defaults to infinite restore plus detailed logging enabled
3131
- per-user installer that always creates a Start Menu shortcut
32+
- future Store preparation through an isolated MSIX preflight package; it is not a published channel yet
3233

3334
## Environment Model
3435

@@ -121,13 +122,18 @@ For real tray validation, run the app from a real Windows path.
121122
- dialogs
122123
- shell-open helper
123124
- clipboard helper for diagnostics
124-
- current-user startup registration via the Windows `Run` key
125+
- channel-aware startup registration: `Run` key for the EXE install and startup task for MSIX
125126

126127
- `packaging/windows/`
127128
- `trayffeine.spec`: PyInstaller bundle definition
128129
- `build.ps1`: manual packaging entrypoint
129130
- `Trayffeine.iss`: Inno Setup installer script
130131

132+
- `packaging/msix/`
133+
- `AppxManifest.xml.template`: Store identity placeholders, full-trust entry point, and startup task
134+
- `build.ps1`: isolated unsigned MSIX preflight builder using MakeAppx
135+
- `README.md`: short pointer to the full Store-preparation procedure
136+
131137
- `tests/`
132138
- unit and smoke-style coverage for session, presenter, i18n, logging, tray wiring, service behavior, and Windows integration helpers
133139

@@ -176,6 +182,11 @@ For real tray validation, run the app from a real Windows path.
176182
- the clipboard payload is support text, not localized UI copy
177183
- include version, language, session, methods, key preferences, settings path, and log path
178184

185+
- Keep startup integration channel-aware.
186+
- the EXE/Inno Setup/WinGet install must keep its current-user `Run` key behavior
187+
- an MSIX package must use its declared `TrayffeineStartup` startup task, not a virtualized `Run` key
188+
- do not try to override a startup task that the user disabled in Task Manager
189+
179190
## Persistence Model
180191

181192
Stored settings currently include:
@@ -243,6 +254,7 @@ When changing logging:
243254
- The installer is unsigned.
244255
- Installer changes belong in `packaging/windows/Trayffeine.iss`.
245256
- Keep the release workflow Windows-only for installer generation.
257+
- Keep the MSIX preflight workflow separate from tags, GitHub Releases, and WinGet updates.
246258

247259
## Testing Expectations
248260

@@ -275,6 +287,7 @@ GitHub workflows:
275287

276288
- `CI` runs on push to `main` and on pull requests
277289
- `Preview Build` runs on pull requests and manual dispatch, publishing a Windows installer artifact for testing
290+
- `MSIX Preview Build` runs on relevant pull requests and manual dispatch, publishing only an unsigned preflight artifact
278291
- `Release` runs only on tags `v*`
279292
- stable tags such as `v1.2.0` publish normal releases
280293
- tags matching `v*-beta*` publish GitHub prereleases

CONTRIBUTING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ Windows run as the final confidence step.
4848
- Preserve stable keep-awake method ids: `smart`, `execution-state`, `f15`, `shift`.
4949
- Keep backend lifecycle on the worker thread.
5050
- Timed sessions must never restore after restart.
51-
- `Start with Windows` is per-user and uses the current-user Windows `Run` key.
51+
- `Start with Windows` is per-user. The EXE/Inno Setup path uses the current-user Windows `Run` key;
52+
the MSIX path uses the declared Windows startup task when package identity is present.
5253
- `Presence compatibility` is a separate persisted preference, not a keep-awake method id.
5354
- When presence compatibility is enabled, the saved normal method remains unchanged and the effective backend is `f15`.
5455
- Do not promise Teams or app-presence status. Document this as best effort and keep the stronger sleep-prevention language tied to `Windows API`.
@@ -62,6 +63,7 @@ When changing user-facing behavior, update public and maintainer docs together:
6263
- `CHANGELOG.md` for release-facing highlights
6364
- `docs/ROADMAP.md` when a planned bucket moves or a backlog item is delivered
6465
- `AGENTS.md` when future coding agents need the behavior or architecture rule
66+
- `docs/msix.md` when changing the MSIX package or Store-submission preparation
6567

6668
For tray or support features, include the real Windows validation caveat unless the behavior was actually tested in a real tray session.
6769

@@ -82,3 +84,4 @@ Release automation:
8284
- `Release` runs on tags matching `v*`
8385
- stable tags publish regular releases
8486
- `v*-beta*` tags publish prereleases and skip WinGet submission
87+
- `MSIX Preview Build` creates an unsigned preflight artifact only; it is isolated from tag releases

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ Persisted preferences:
181181
Startup behavior:
182182

183183
- `Start with Windows` is optional and disabled by default
184-
- when enabled, Trayffeine registers itself in the current-user Windows `Run` key
184+
- the EXE/Inno Setup/WinGet install registers itself in the current-user Windows `Run` key
185+
- the future MSIX package uses its declared Windows startup task instead, while keeping the same tray preference
185186
- timed sessions never resume after a restart
186187
- infinite mode can resume if it was active when the app last saved state
187188

@@ -276,6 +277,7 @@ Environment override:
276277
- `src/trayffeine/win32_tray.py`: Windows-specific tray wrapper for double-click handling
277278
- `src/trayffeine/windows.py`: Windows keep-awake backends, dialogs, mutex, shell helpers, startup registration
278279
- `packaging/windows/`: PyInstaller spec, build script, Inno Setup installer script
280+
- `packaging/msix/`: isolated MSIX preflight build and manifest template for a future Store submission
279281
- `tests/`: unit and smoke-style tests
280282
- [CONTRIBUTING.md](CONTRIBUTING.md): contributor workflow and validation notes
281283
- [docs/ROADMAP.md](docs/ROADMAP.md): internal release buckets and backlog notes
@@ -340,6 +342,13 @@ Preview test flow:
340342
- download the uploaded installer artifact from the workflow run
341343
- test that installer on Windows before tagging a release
342344

345+
### Future Microsoft Store MSIX package
346+
347+
MSIX preparation is separate from the existing public installer and WinGet channel. The `MSIX Preview
348+
Build` workflow creates an unsigned, non-Store preflight artifact for pull requests and never creates
349+
a GitHub Release. See [docs/msix.md](docs/msix.md) for local build steps, package behavior, Partner
350+
Center identity values, and the later signing/submission checklist.
351+
343352
## Validation
344353

345354
Expected local validation:

docs/msix.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Microsoft Store / MSIX pilot
2+
3+
Trayffeine is prepared for a future Microsoft Store submission as a full-trust MSIX desktop app.
4+
This is preparation only: there is no Store listing, submission, GitHub Release upload, or WinGet
5+
change in this repository state.
6+
7+
## What remains unchanged
8+
9+
- The public installer remains the per-user Inno Setup `.exe` built by
10+
`packaging/windows/build.ps1`.
11+
- GitHub Release tags still publish only that installer and follow the existing WinGet update path.
12+
- The new MSIX workflow never runs on tags and never creates a GitHub Release.
13+
- No project version is changed just to create an MSIX package.
14+
15+
The MSIX route is intentionally an x64 pilot for Trayffeine. Add other architectures only after a
16+
real Store acceptance test shows they are needed.
17+
18+
## Package design and behavior
19+
20+
`packaging/msix/build.ps1` creates an MSIX-only PyInstaller bundle under `dist/msix/`, stages it
21+
with `AppxManifest.xml`, and uses the Windows SDK `MakeAppx.exe` to pack and unpack the result. The
22+
unpack step verifies the identity, full-trust executable, declared startup task, and required tile
23+
assets. The script does not sign, publish, install, or submit the package.
24+
25+
The manifest declares `Windows.FullTrustApplication` and `runFullTrust`, which keeps the existing
26+
Win32 tray implementation, native dialogs, and keep-awake backends in their full-trust desktop
27+
context. It also declares the `TrayffeineStartup` `windows.startupTask` extension.
28+
29+
Startup behavior is intentionally channel-aware:
30+
31+
- The existing EXE/Inno Setup/WinGet install continues to use the current-user `Run` key.
32+
- When Trayffeine has an MSIX package identity, the same tray preference uses the declared Windows
33+
startup task instead. This avoids relying on a virtualized `Run` key inside the package.
34+
- The task starts disabled. Turning on `Preferences > Start with Windows` requests its enablement.
35+
Windows exposes it in Task Manager's Startup tab. If the user disables it there, Windows does not
36+
allow the app to turn it back on programmatically; the next launch reconciles the tray preference
37+
to off.
38+
39+
Settings and logs continue to use the existing `%LOCALAPPDATA%\Trayffeine` code path. MSIX manages
40+
that state per user and preserves it through package updates; a full uninstall can remove
41+
package-managed state. Test any desired migration from an existing EXE install explicitly during
42+
acceptance, rather than assuming that the two install channels share all state.
43+
44+
Do not run the legacy EXE and MSIX package as simultaneous acceptance targets: both deliberately use
45+
the same single-instance mutex. Disable legacy autostart before testing MSIX autostart.
46+
47+
## Build an unsigned preflight package locally
48+
49+
Requirements:
50+
51+
- Windows with the Windows 10 or 11 SDK MSIX packaging tools (`MakeAppx.exe`)
52+
- Python 3.13
53+
54+
```powershell
55+
py -3.13 -m venv .venv
56+
.venv\Scripts\Activate.ps1
57+
python -m pip install -e .[build,msix]
58+
python scripts\generate_assets.py
59+
60+
powershell -ExecutionPolicy Bypass -File packaging\msix\build.ps1 `
61+
-IdentityName "Trayffeine.Preview" `
62+
-Publisher "CN=Trayffeine Preview" `
63+
-PublisherDisplayName "Trayffeine Preview" `
64+
-Clean
65+
```
66+
67+
The command creates `dist/msix/Trayffeine-<version>-x64.msix`, plus the staged package at
68+
`dist/msix/staging`. It is deliberately unsigned. The preview identity is only suitable for package
69+
structure validation; do not upload it to Partner Center.
70+
71+
The staging and verification directories are recreated on every build, even without `-Clean`.
72+
Passing `-Clean` additionally removes the previous PyInstaller work and all MSIX output first.
73+
74+
For an interactive local package-context smoke test, register the staged manifest from an appropriate
75+
development-enabled Windows machine:
76+
77+
```powershell
78+
Add-AppxPackage -Register (Resolve-Path .\dist\msix\staging\AppxManifest.xml)
79+
```
80+
81+
This worktree's host intentionally did not perform that registration: Windows returned `0x80073CFF`
82+
because it does not allow unsigned developer package registration. Do not enable Developer Mode or
83+
change sideload/security policy solely for this pilot. This is the only unperformed local installation
84+
test; run it later on a disposable, development-approved Windows profile.
85+
86+
Then launch Trayffeine from Start and verify tray appearance, a timed session, infinite restore,
87+
settings persistence across relaunch, and the Startup task. Test package removal separately on a
88+
non-production user profile. A signed package is only necessary for direct MSIX sideloading; it is
89+
not needed for this preflight or a Microsoft Store MSIX submission.
90+
91+
## CI preflight
92+
93+
`MSIX Preview Build` runs for relevant pull requests and manual dispatches on `windows-latest`. It
94+
uses a non-Store preview identity and uploads the unsigned `.msix` only as a workflow artifact. It
95+
also rebuilds without `-Clean` after adding sentinel files, verifying that stale staging and
96+
verification content is removed. It does not need Partner Center credentials, certificates, GitHub
97+
Release permissions, or WinGet credentials.
98+
99+
## Partner Center values required later
100+
101+
Before the first real submission, use Partner Center to create an **MSIX/PWA** product and reserve
102+
the Store name. Copy these exact package-identity values into the real build command:
103+
104+
- `Identity Name` / package identity name
105+
- `Publisher` (the exact X.500 publisher string supplied by Partner Center)
106+
- reserved display name and the publisher display name, if they differ from the defaults
107+
108+
The public display name is not a substitute for either identity field. Build the submission candidate
109+
only after those values are known:
110+
111+
```powershell
112+
powershell -ExecutionPolicy Bypass -File packaging\msix\build.ps1 `
113+
-IdentityName "<Partner Center identity name>" `
114+
-Publisher "<Partner Center publisher>" `
115+
-DisplayName "<Reserved Store name>" `
116+
-PublisherDisplayName "<Partner Center publisher display name>" `
117+
-Clean
118+
```
119+
120+
The script reads `pyproject.toml` by default and maps the current semantic version, for example
121+
`1.2.0`, to the four-part MSIX version `1.2.0.0`. A later Store update must increase that MSIX
122+
version; this preparation does not require a version bump.
123+
124+
Before uploading the real submission candidate, run the current Windows App Certification Kit from
125+
an elevated command prompt in an active user session. Use the package built with the exact Partner
126+
Center identity, and review the generated report rather than assuming that a successful MakeAppx
127+
pack/unpack is equivalent to Store certification:
128+
129+
```powershell
130+
$appCert = "${env:ProgramFiles(x86)}\Windows Kits\10\App Certification Kit\appcert.exe"
131+
& $appCert reset
132+
& $appCert test `
133+
-appxpackagepath ".\dist\msix\Trayffeine-<version>-x64.msix" `
134+
-reportoutputpath ".\dist\msix\wack-report.xml"
135+
```
136+
137+
Finish the required Partner Center metadata before submission: pricing and availability, category and
138+
capability declarations, age rating, Store description, screenshots, Store listing artwork, support
139+
details, and a privacy-policy URL if the published data practices require one. Explain the
140+
`runFullTrust` declaration as the packaging requirement for this existing Win32 tray application.
141+
142+
For Microsoft Store MSIX distribution, Microsoft signs the submitted package after certification, so
143+
no certificate, PFX, or signing secret belongs in this repository or workflow. For private signed
144+
sideload tests, use a local test certificate whose subject exactly matches the manifest publisher and
145+
trust it only on the test machine.
146+
147+
Useful official references:
148+
149+
- [Manual desktop MSIX packaging](https://learn.microsoft.com/windows/msix/desktop/desktop-to-uwp-manual-conversion)
150+
- [Desktop startup task extension](https://learn.microsoft.com/windows/apps/desktop/modernize/desktop-to-uwp-extensions#start-an-executable-file-when-users-log-into-windows)
151+
- [MakeAppx command-line packaging](https://learn.microsoft.com/windows/msix/package/create-app-package-with-makeappx-tool)
152+
- [Windows App Certification Kit](https://learn.microsoft.com/windows/uwp/debug-test-perf/windows-app-certification-kit)
153+
- [MSIX signing options](https://learn.microsoft.com/windows/msix/package/signing-package-overview)
154+
- [Packaged desktop app state and AppData behavior](https://learn.microsoft.com/windows/msix/desktop/desktop-to-uwp-behind-the-scenes)

0 commit comments

Comments
 (0)