docs: update MSIX spec (PFN deep link, .drc, Associate tab) and add implementation plan

This commit is contained in:
SoftFever
2026-06-11 03:21:50 +08:00
parent 5179e50aff
commit 8db5255a39
2 changed files with 667 additions and 16 deletions

View File

@@ -0,0 +1,645 @@
# MSIX Microsoft Store Build Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build an unsigned MSIX Store package of OrcaSlicer in CI from the existing Windows install tree, and make the app behave correctly in packaged context (no self-update, no runtime registry-based file associations).
**Architecture:** A hand-written `AppxManifest.xml` template + PowerShell pack script under `scripts/msix/` (precedent: `scripts/flatpak/`), one new CI step in the Windows job of `.github/workflows/build_orca.yml`, and a small packaged-context detection helper in `src/slic3r/GUI/GUI_Utils.{hpp,cpp}` used to gate the updater and association code. Everything is additive: NSIS/portable outputs are untouched, all runtime gates are no-ops in classic builds.
**Tech Stack:** PowerShell 5.1+ (System.Drawing, makeappx from Windows SDK), GitHub Actions, C++17/wxWidgets (Win32 `GetCurrentPackageFullName`/`GetCurrentPackageFamilyName` from `appmodel.h`, kernel32).
**Spec:** `docs/superpowers/specs/2026-06-11-msix-store-build-design.md`
**Branch:** `msix-store-build` (already created; spec is committed on it)
**Why no TDD here:** none of this is unit-testable in this repo — the C++ changes live in the wxWidgets GUI module which no test target links (tests cover `libslic3r`), and the behavior under test is Windows shell/packaging behavior. Each task instead has concrete verification steps (script output checks, XML well-formedness, full app build), and Task 8 collects the manual verification documented in the PR per the repo's review guidelines.
**Windows build command** (required for any task touching C++; plain shells fail without the VS environment — run via PowerShell):
```
cmd /c '"C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvars64.bat" >nul && cmake --build build --config RelWithDebInfo --target OrcaSlicer 2>&1'
```
The `'vswhere.exe' is not recognized` line vcvars prints is harmless. Expected: build completes with exit code 0 (warnings OK). Incremental builds are fast when warm.
---
### Task 1: MSIX logo assets + generator script
**Files:**
- Create: `scripts/msix/generate_assets.ps1`
- Create (generated): `scripts/msix/assets/Square150x150Logo.png`, `scripts/msix/assets/Square44x44Logo.png`, `scripts/msix/assets/Square44x44Logo.targetsize-44_altform-unplated.png`, `scripts/msix/assets/StoreLogo.png`
- [ ] **Step 1: Write the generator script**
Create `scripts/msix/generate_assets.ps1` with exactly this content:
```powershell
# Generates the MSIX package logo assets from the 192px master logo.
# Run once on Windows (re-run if the logo changes), then commit the PNGs in assets/.
# Scale-200 variants would need a >192px master; regenerate from OrcaSlicer.svg if ever needed.
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
$source = Join-Path $repoRoot 'resources\images\OrcaSlicer_192px.png'
$outDir = Join-Path $PSScriptRoot 'assets'
New-Item -ItemType Directory -Force $outDir | Out-Null
$sizes = @{
'Square150x150Logo.png' = 150
'Square44x44Logo.png' = 44
'Square44x44Logo.targetsize-44_altform-unplated.png' = 44
'StoreLogo.png' = 50
}
$src = [System.Drawing.Image]::FromFile($source)
foreach ($name in $sizes.Keys) {
$px = $sizes[$name]
$bmp = New-Object System.Drawing.Bitmap($px, $px)
$gfx = [System.Drawing.Graphics]::FromImage($bmp)
$gfx.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$gfx.DrawImage($src, 0, 0, $px, $px)
$gfx.Dispose()
$bmp.Save((Join-Path $outDir $name), [System.Drawing.Imaging.ImageFormat]::Png)
$bmp.Dispose()
Write-Output "Wrote $name (${px}x${px})"
}
$src.Dispose()
```
- [ ] **Step 2: Run it**
Run (PowerShell, from repo root): `./scripts/msix/generate_assets.ps1`
Expected output: four `Wrote <name> (<px>x<px>)` lines, no errors.
- [ ] **Step 3: Verify the generated dimensions**
```powershell
Add-Type -AssemblyName System.Drawing
Get-ChildItem scripts/msix/assets/*.png | ForEach-Object { $i = [System.Drawing.Image]::FromFile($_.FullName); "$($_.Name): $($i.Width)x$($i.Height)"; $i.Dispose() }
```
Expected: `Square150x150Logo.png: 150x150`, `Square44x44Logo.png: 44x44`, `Square44x44Logo.targetsize-44_altform-unplated.png: 44x44`, `StoreLogo.png: 50x50`.
- [ ] **Step 4: Commit**
```bash
git add scripts/msix/generate_assets.ps1 scripts/msix/assets
git commit -m "ci: add MSIX logo asset generator and generated assets"
```
---
### Task 2: AppxManifest.xml template
**Files:**
- Create: `scripts/msix/AppxManifest.xml`
- [ ] **Step 1: Write the manifest template**
Create `scripts/msix/AppxManifest.xml` with exactly this content. The four `@...@` tokens are substituted by `build_msix.ps1` (Task 3). Namespace URIs and the virtualization/MigrationProgIds element shapes were verified against learn.microsoft.com (flexible-virtualization and element-rescap3-migrationprogids pages) on 2026-06-11. `MigrationProgId` is `Orca.Slicer.1` — note the classic build writes the registry ProgID with a leading space (`GUI_App.cpp:9119`), which MigrationProgIds cannot express, so migration from existing NSIS installs is best-effort (Windows shows both apps in "Open with"); deliberately NOT fixed here.
```xml
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:rescap3="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities/3"
xmlns:desktop6="http://schemas.microsoft.com/appx/manifest/desktop/windows10/6"
xmlns:virtualization="http://schemas.microsoft.com/appx/manifest/virtualization/windows10"
IgnorableNamespaces="uap rescap rescap3 desktop6 virtualization">
<Identity Name="@MSIX_IDENTITY_NAME@"
Publisher="@MSIX_PUBLISHER@"
Version="@MSIX_VERSION@"
ProcessorArchitecture="x64" />
<Properties>
<DisplayName>OrcaSlicer</DisplayName>
<PublisherDisplayName>@MSIX_PUBLISHER_DISPLAY_NAME@</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
<!-- Keep config in the real %APPDATA%\OrcaSlicer so it survives uninstall and
is shared with the classic (NSIS/portable) install.
Win10 1903+: coarse switch disables AppData write virtualization entirely.
Win11+: fine-grained exclusion below takes precedence over the coarse switch. -->
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
<virtualization:FileSystemWriteVirtualization>
<virtualization:ExcludedDirectories>
<virtualization:ExcludedDirectory>$(KnownFolder:RoamingAppData)\OrcaSlicer</virtualization:ExcludedDirectory>
</virtualization:ExcludedDirectories>
</virtualization:FileSystemWriteVirtualization>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18362.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="en-us" />
</Resources>
<Applications>
<Application Id="OrcaSlicer" Executable="orca-slicer.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="OrcaSlicer"
Description="Open-source slicer for FDM 3D printers"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
<Extensions>
<uap:Extension Category="windows.fileTypeAssociation">
<uap:FileTypeAssociation Name="orcaslicer-models">
<uap:SupportedFileTypes>
<uap:FileType>.3mf</uap:FileType>
<uap:FileType>.stl</uap:FileType>
<uap:FileType>.step</uap:FileType>
<uap:FileType>.stp</uap:FileType>
<uap:FileType>.gcode</uap:FileType>
<uap:FileType>.drc</uap:FileType>
</uap:SupportedFileTypes>
<rescap3:MigrationProgIds>
<rescap3:MigrationProgId>Orca.Slicer.1</rescap3:MigrationProgId>
</rescap3:MigrationProgIds>
</uap:FileTypeAssociation>
</uap:Extension>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="orcaslicer" />
</uap:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources" />
</Capabilities>
</Package>
```
- [ ] **Step 2: Verify XML is well-formed**
Run (PowerShell): `[xml](Get-Content scripts/msix/AppxManifest.xml -Raw) | Out-Null; 'XML OK'`
Expected: `XML OK` (no exception).
- [ ] **Step 3: Commit**
```bash
git add scripts/msix/AppxManifest.xml
git commit -m "ci: add MSIX AppxManifest template"
```
---
### Task 3: build_msix.ps1 pack script
**Files:**
- Create: `scripts/msix/build_msix.ps1`
- [ ] **Step 1: Write the pack script**
Create `scripts/msix/build_msix.ps1` with exactly this content:
```powershell
<#
Builds the unsigned MSIX Store package from an existing install tree.
The package is intentionally NOT signed: the Microsoft Store strips and
re-signs uploads with Microsoft's certificate. For local installs use
Developer Mode loose-layout registration instead:
./scripts/msix/build_msix.ps1 -StageOnly
Add-AppxPackage -Register <staging>\AppxManifest.xml
Requires the Windows SDK (makeappx.exe) unless -StageOnly is used.
#>
param(
[string]$InstallDir = "build/OrcaSlicer",
[string]$OutputPath = "build/OrcaSlicer_Windows_MSIX.msix",
[string]$StagingDir = "",
[switch]$StageOnly,
[string]$IdentityName = "OrcaSlicer.Dev",
[string]$Publisher = "CN=00000000-0000-0000-0000-000000000000",
[string]$PublisherDisplayName = "OrcaSlicer (dev)"
)
$ErrorActionPreference = 'Stop'
$repoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
# MSIX version = MAJOR.MINOR.PATCH.0 from the SoftFever_VERSION semver triplet
# (Store requires the revision field to be 0).
$versionContent = Get-Content (Join-Path $repoRoot 'version.inc') -Raw
if ($versionContent -notmatch 'set\(SoftFever_VERSION "(\d+)\.(\d+)\.(\d+)') {
throw "Could not parse SoftFever_VERSION from version.inc"
}
$msixVersion = "$($Matches[1]).$($Matches[2]).$($Matches[3]).0"
Write-Output "MSIX version: $msixVersion"
if (-not (Test-Path (Join-Path $InstallDir 'orca-slicer.exe'))) {
throw "orca-slicer.exe not found in '$InstallDir' - build the install tree first"
}
if ([string]::IsNullOrEmpty($StagingDir)) {
$StagingDir = Join-Path ([System.IO.Path]::GetTempPath()) 'orca-msix-staging'
}
if (Test-Path $StagingDir) { Remove-Item $StagingDir -Recurse -Force }
New-Item -ItemType Directory -Force $StagingDir | Out-Null
Copy-Item -Path (Join-Path $InstallDir '*') -Destination $StagingDir -Recurse
Copy-Item -Path (Join-Path $PSScriptRoot 'assets') -Destination (Join-Path $StagingDir 'Assets') -Recurse
$manifest = Get-Content (Join-Path $PSScriptRoot 'AppxManifest.xml') -Raw
$manifest = $manifest.Replace('@MSIX_VERSION@', $msixVersion)
$manifest = $manifest.Replace('@MSIX_IDENTITY_NAME@', $IdentityName)
$manifest = $manifest.Replace('@MSIX_PUBLISHER@', $Publisher)
$manifest = $manifest.Replace('@MSIX_PUBLISHER_DISPLAY_NAME@', $PublisherDisplayName)
Set-Content -Path (Join-Path $StagingDir 'AppxManifest.xml') -Value $manifest -Encoding utf8
if ($StageOnly) {
Write-Output "Staged loose layout at: $StagingDir"
exit 0
}
$makeappx = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.*\x64\makeappx.exe" -ErrorAction SilentlyContinue |
Sort-Object { [version]$_.Directory.Parent.Name } -Descending |
Select-Object -First 1 -ExpandProperty FullName
if (-not $makeappx) {
throw "makeappx.exe not found under '${env:ProgramFiles(x86)}\Windows Kits\10\bin' - install the Windows SDK"
}
Write-Output "Using makeappx: $makeappx"
& $makeappx pack /d $StagingDir /p $OutputPath /o
if ($LASTEXITCODE -ne 0) { throw "makeappx pack failed with exit code $LASTEXITCODE" }
Write-Output "Packed: $OutputPath"
```
- [ ] **Step 2: Verify locally (conditional)**
If `build/OrcaSlicer/orca-slicer.exe` exists on this machine, run from repo root:
`./scripts/msix/build_msix.ps1 -OutputPath build/OrcaSlicer_test.msix`
Expected: `MSIX version: 2.4.0.0` (current `version.inc`), `Using makeappx: ...`, makeappx output ending in package creation succeeded, `Packed: build/OrcaSlicer_test.msix`. Then delete the test package: `Remove-Item build/OrcaSlicer_test.msix`.
If the install tree does NOT exist, run only the parse/staging guard paths instead:
`./scripts/msix/build_msix.ps1 -InstallDir nonexistent` — expected: throws `orca-slicer.exe not found in 'nonexistent' - build the install tree first`. Full pack then gets verified by the CI run (Task 8).
- [ ] **Step 3: Commit**
```bash
git add scripts/msix/build_msix.ps1
git commit -m "ci: add MSIX packaging script"
```
---
### Task 4: CI step in build_orca.yml
**Files:**
- Modify: `.github/workflows/build_orca.yml` (insert after the "Create installer Win" step, currently lines 295-299, before "Pack app")
- [ ] **Step 1: Add the MSIX build + upload steps**
In `.github/workflows/build_orca.yml`, directly after this existing block:
```yaml
- name: Create installer Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}/build
run: |
cpack -G NSIS
```
insert:
```yaml
- name: Build MSIX Store package Win
if: runner.os == 'Windows' && !vars.SELF_HOSTED
working-directory: ${{ github.workspace }}
shell: pwsh
run: |
./scripts/msix/build_msix.ps1 `
-InstallDir "${{ github.workspace }}/build/OrcaSlicer" `
-OutputPath "${{ github.workspace }}/build/OrcaSlicer_Windows_MSIX_${{ env.ver }}.msix" `
-IdentityName "${{ vars.ORCA_MSIX_IDENTITY_NAME || 'OrcaSlicer.Dev' }}" `
-Publisher "${{ vars.ORCA_MSIX_PUBLISHER || 'CN=00000000-0000-0000-0000-000000000000' }}" `
-PublisherDisplayName "${{ vars.ORCA_MSIX_PUBLISHER_DISPLAY_NAME || 'OrcaSlicer (dev)' }}"
```
and after the existing "Upload artifacts Win installer" step (uploads `build/OrcaSlicer*.exe`), insert:
```yaml
- name: Upload artifacts Win MSIX
if: runner.os == 'Windows' && !vars.SELF_HOSTED
uses: actions/upload-artifact@v7
with:
name: OrcaSlicer_Windows_MSIX_${{ env.ver }}
path: ${{ github.workspace }}/build/OrcaSlicer_Windows_MSIX_${{ env.ver }}.msix
```
Notes: gating mirrors the NSIS installer step (`!vars.SELF_HOSTED` — github-hosted runners are guaranteed to have the Windows SDK). The unset-variable fallbacks (`|| '...'`) keep fork/pre-reservation builds green with placeholder identity. Do NOT touch any existing step.
- [ ] **Step 2: Verify YAML parses**
Run (PowerShell): `python -c "import yaml; yaml.safe_load(open('.github/workflows/build_orca.yml', encoding='utf-8')); print('YAML OK')"`
Expected: `YAML OK`. (If python/pyyaml is unavailable, fall back to careful diff review — `git diff .github/workflows/build_orca.yml` — checking indentation matches sibling steps at 6 spaces; final validation is the CI run in Task 8.)
- [ ] **Step 3: Commit**
```bash
git add .github/workflows/build_orca.yml
git commit -m "ci: build MSIX Store package in Windows job"
```
---
### Task 5: packaged-context helpers in GUI_Utils
**Files:**
- Modify: `src/slic3r/GUI/GUI_Utils.hpp` (declarations after `format_nozzle_diameter`, ~line 69)
- Modify: `src/slic3r/GUI/GUI_Utils.cpp` (include + implementations after `format_nozzle_diameter`, ~line 47)
- [ ] **Step 1: Declare the helpers**
In `src/slic3r/GUI/GUI_Utils.hpp`, after the line
`wxString format_nozzle_diameter(float diameter);`
add:
```cpp
// True when running inside an MSIX package (Microsoft Store build); always false on non-Windows.
bool is_running_in_msix();
// Opens the Microsoft Store product page for the current package. No-op when not packaged.
void open_ms_store_product_page();
```
- [ ] **Step 2: Implement them**
In `src/slic3r/GUI/GUI_Utils.cpp`, extend the existing Windows include block (lines 10-14)
```cpp
#ifdef _WIN32
#include <Windows.h>
#include "libslic3r/AppConfig.hpp"
#include <wx/msw/registry.h>
#endif // _WIN32
```
to
```cpp
#ifdef _WIN32
#include <Windows.h>
#include <appmodel.h>
#include "libslic3r/AppConfig.hpp"
#include <wx/msw/registry.h>
#endif // _WIN32
```
and add `#include <wx/utils.h>` to the wx include group (after `#include <wx/display.h>`, line 26).
Then, after the `format_nozzle_diameter` function body (ends ~line 47), add:
```cpp
bool is_running_in_msix()
{
#ifdef _WIN32
// Null-buffer probe: returns ERROR_INSUFFICIENT_BUFFER when packaged,
// APPMODEL_ERROR_NO_PACKAGE when running unpackaged.
static const bool packaged = []() {
UINT32 length = 0;
return ::GetCurrentPackageFullName(&length, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
}();
return packaged;
#else
return false;
#endif
}
void open_ms_store_product_page()
{
#ifdef _WIN32
UINT32 length = 0;
if (::GetCurrentPackageFamilyName(&length, nullptr) != ERROR_INSUFFICIENT_BUFFER)
return;
std::wstring family_name(length, L'\0');
if (::GetCurrentPackageFamilyName(&length, family_name.data()) != ERROR_SUCCESS)
return;
family_name.resize(length > 0 ? length - 1 : 0); // drop the terminating null
wxLaunchDefaultBrowser(wxString(L"ms-windows-store://pdp/?PFN=") + family_name.c_str());
#endif
}
```
- [ ] **Step 3: Build**
Run the Windows build command from the plan header (target `OrcaSlicer`). Expected: exit code 0.
- [ ] **Step 4: Commit**
```bash
git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/GUI_Utils.cpp
git commit -m "feat: add MSIX packaged-context detection helpers"
```
---
### Task 6: gate the updater in packaged context
**Files:**
- Modify: `src/slic3r/GUI/GUI_App.cpp` (~line 934, startup auto-check)
- Modify: `src/slic3r/GUI/MainFrame.cpp` (~line 2578, Help menu "Check for Updates")
Both files already include `GUI_Utils.hpp` (GUI_App.cpp directly; MainFrame.cpp via MainFrame.hpp) and are inside `namespace Slic3r::GUI`, so the helpers are callable unqualified.
- [ ] **Step 1: Gate the startup auto-check**
In `src/slic3r/GUI/GUI_App.cpp`, change (inside the `CallAfter` at ~line 934):
```cpp
this->check_new_version_sf();
```
to:
```cpp
// Store builds update through the Microsoft Store, never self-update.
if (!is_running_in_msix())
this->check_new_version_sf();
```
- [ ] **Step 2: Redirect the manual menu check**
In `src/slic3r/GUI/MainFrame.cpp`, change (~lines 2578-2583):
```cpp
append_menu_item(helpMenu, wxID_ANY, _L("Check for Updates"), _L("Check for Updates"),
[](wxCommandEvent&) {
wxGetApp().check_new_version_sf(true, 1);
}, "", nullptr, []() {
return true;
});
```
to:
```cpp
append_menu_item(helpMenu, wxID_ANY, _L("Check for Updates"), _L("Check for Updates"),
[](wxCommandEvent&) {
if (is_running_in_msix())
open_ms_store_product_page();
else
wxGetApp().check_new_version_sf(true, 1);
}, "", nullptr, []() {
return true;
});
```
- [ ] **Step 3: Build**
Run the Windows build command from the plan header (target `OrcaSlicer`). Expected: exit code 0.
- [ ] **Step 4: Commit**
```bash
git add src/slic3r/GUI/GUI_App.cpp src/slic3r/GUI/MainFrame.cpp
git commit -m "feat: suppress self-update in MSIX Store build"
```
---
### Task 7: gate runtime file associations in packaged context
**Files:**
- Modify: `src/slic3r/GUI/GUI_App.cpp` (`associate_files` ~9112, `disassociate_files` ~9137, `associate_url` ~9188, `disassociate_url` ~9213 — line numbers shifted ~+2 by Task 6)
- Modify: `src/slic3r/GUI/Preferences.cpp` (Associate tab, ~lines 1848-1883)
- [ ] **Step 1: Early-return in the four association functions**
In `src/slic3r/GUI/GUI_App.cpp`, add an early return as the first statement inside the `#ifdef WIN32` block of each of the four functions. In `associate_files`:
```cpp
void GUI_App::associate_files(std::wstring extend)
{
#ifdef WIN32
// MSIX: shell integration is declared in the package manifest; registry
// writes from a packaged process are virtualized and invisible to the shell.
if (is_running_in_msix())
return;
wchar_t app_path[MAX_PATH];
```
In `disassociate_files`, `associate_url`, and `disassociate_url`, add the same guard without repeating the comment — first statement inside `#ifdef WIN32`:
```cpp
if (is_running_in_msix())
return;
```
(`associate_url` keeps its Linux `#elif` branch untouched — the guard goes only in the WIN32 branch.)
- [ ] **Step 2: Hide the Associate tab in Preferences**
In `src/slic3r/GUI/Preferences.cpp`, wrap the body of the Associate-tab block (lines 1848-1883). Change:
```cpp
#ifdef _WIN32
m_pref_tabs->AppendItem(_L("Associate"));
```
to:
```cpp
#ifdef _WIN32
// MSIX: associations are declared in the package manifest and defaults are
// managed by Windows Settings; the runtime registry toggles below cannot work.
if (!is_running_in_msix()) {
m_pref_tabs->AppendItem(_L("Associate"));
```
and change the end of that block:
```cpp
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
#endif // _WIN32
//////////////////////////
//// DEVELOPER TAB
```
to:
```cpp
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
}
#endif // _WIN32
//////////////////////////
//// DEVELOPER TAB
```
(Keep the inner lines at their current indentation — a re-indent would bloat the diff; the `{`/`}` pair at statement level matches the file's pragmatic style. Skipping the whole block keeps `m_pref_tabs` items and `f_sizers` entries paired, which is what the tab-switching logic relies on. This also removes the only `check_url_association` consumers, so no other prompt suppression is needed.)
- [ ] **Step 3: Build**
Run the Windows build command from the plan header (target `OrcaSlicer`). Expected: exit code 0.
- [ ] **Step 4: Sanity-check classic behavior is unchanged**
Launch the freshly built classic exe: `build/src/RelWithDebInfo/orca-slicer.exe` (path per local layout). Open Preferences — the Associate tab must still be present with all checkboxes (we are not packaged, so `is_running_in_msix()` is false). Close the app.
- [ ] **Step 5: Commit**
```bash
git add src/slic3r/GUI/GUI_App.cpp src/slic3r/GUI/Preferences.cpp
git commit -m "feat: suppress runtime file associations in MSIX Store build"
```
---
### Task 8: end-to-end verification + PR
No new files. This is the verification gate the PR description documents (repo guideline: behavior changes need documented verification).
- [ ] **Step 1: Push branch and open a draft PR**
```bash
git push -u origin msix-store-build
gh pr create --draft --title "Add Microsoft Store MSIX package build" --body "<composed per below>"
```
(Compose the body first — pass it via `--body` with a here-string, not `--body-file -`, which blocks on stdin in non-interactive shells.) PR body must include: motivation (SAC blocks unsigned NSIS installer; Store re-signs), summary of the four-token identity scheme + the three repo variables to set after Partner Center name reservation (`ORCA_MSIX_IDENTITY_NAME`, `ORCA_MSIX_PUBLISHER`, `ORCA_MSIX_PUBLISHER_DISPLAY_NAME`), the spec path, and the manual-verification checklist from Steps 3-5 below with results.
- [ ] **Step 2: Verify CI**
Wait for the PR's Windows job. Expected: new "Build MSIX Store package Win" step succeeds with placeholder identity; artifact `OrcaSlicer_Windows_MSIX_PR<N>` contains one `.msix`; NSIS installer and portable zip artifacts still produced unchanged.
- [ ] **Step 3: Local loose-layout install test (Developer Mode required)**
```powershell
./scripts/msix/build_msix.ps1 -StageOnly
Add-AppxPackage -Register "$env:TEMP\orca-msix-staging\AppxManifest.xml"
```
Then verify, with results recorded in the PR:
- App launches from Start menu ("OrcaSlicer (dev)" entry).
- Preferences has NO Associate tab; Help > Check for Updates opens the Store app (placeholder PFN won't resolve to a listing — that's expected pre-reservation; the point is it does NOT run the classic downloader).
- Right-click a `.3mf` file > Open with — packaged OrcaSlicer is listed and opens the file. Visit `orcaslicer://` from a browser — packaged app activates.
- Single-instance hand-off: with the packaged app running, open a second `.3mf` the same way — it loads in the existing instance instead of spawning a new one.
- Config interop: profiles created by the classic install are visible in the packaged app and vice versa (`%APPDATA%\OrcaSlicer` is shared; requires Win11 for the fine-grained exclusion on a loose layout).
- Bambu network plugin: install/download plugin from inside the packaged app succeeds and loads.
- Uninstall the packaged app (right-click in Start) — `%APPDATA%\OrcaSlicer` still exists.
Remove the test registration when done: `Get-AppxPackage *OrcaSlicer.Dev* | Remove-AppxPackage`
- [ ] **Step 4: WACK (optional but recommended)**
On a machine with the Windows SDK: pack a real `.msix` (Task 3 script), then run the Windows App Certification Kit GUI against it. Record pass/fail + any triaged warnings in the PR. (Restricted-capability warnings about `unvirtualizedResources` are expected and justified at submission time.)
- [ ] **Step 5: Mark PR ready for review**
```bash
gh pr ready
```
---
## Post-merge (NOT part of this plan — tracked in MSIX_STORE_HANDOFF.md)
Once the Partner Center company account is approved: reserve the app name, set the three repo variables from Product identity, download the next CI `.msix` artifact, and do the first manual Store submission (listing, IARC, `runFullTrust` + `unvirtualizedResources` justifications). True SAC verification happens post-certification via a private-audience listing.

View File

@@ -56,12 +56,16 @@ schema reference during implementation; `makeappx`/WACK validate):
at `src/CMakeLists.txt:189`). at `src/CMakeLists.txt:189`).
- Extensions: - Extensions:
- `windows.fileTypeAssociation` for `.3mf`, `.stl`, `.step`/`.stp`, - `windows.fileTypeAssociation` for `.3mf`, `.stl`, `.step`/`.stp`,
`.gcode`. Note: `.gcode` is opt-in in the classic build `.gcode`, `.drc`. Note: `.gcode` and `.drc` are opt-in in the
(`associate_gcode`); manifest declarations are static, so it is classic build; manifest declarations are static, so they are
always declared. Declaring an FTA only adds OrcaSlicer to "Open always declared. Declaring an FTA only adds OrcaSlicer to "Open
with" — the user controls defaults via Windows Settings, so this is with" — the user controls defaults via Windows Settings, so this is
not a behavior regression. not a behavior regression.
- `windows.protocol` for `orcaslicer://`. - `windows.protocol` for `orcaslicer://`. The classic build also
offers opt-in handlers for `prusaslicer://`, `bambustudio://` and
`cura://` (Preferences "Associate" tab); those are NOT declared in
the manifest for now (possible follow-up) and their toggles are
hidden in packaged context.
- `rescap3:MigrationProgIds` declaring `Orca.Slicer.1`. - `rescap3:MigrationProgIds` declaring `Orca.Slicer.1`.
KNOWN LIMITATION: the classic build writes its ProgID with a KNOWN LIMITATION: the classic build writes its ProgID with a
leading space (`L" Orca.Slicer.1"`, `GUI_App.cpp:9119`), which leading space (`L" Orca.Slicer.1"`, `GUI_App.cpp:9119`), which
@@ -114,23 +118,25 @@ non-Windows.
- Skip the startup auto-check (`check_new_version_sf()` call at - Skip the startup auto-check (`check_new_version_sf()` call at
`GUI_App.cpp:934`) when packaged. `GUI_App.cpp:934`) when packaged.
- The manual "Check for updates" menu action (`MainFrame.cpp:2580`) - The manual "Check for updates" menu action (`MainFrame.cpp:2580`)
opens the Store listing (`ms-windows-store://pdp/?ProductId=...`, opens the Store listing via `ms-windows-store://pdp/?PFN=<family>`,
ProductId from a build-time define fed by repo variable where the package family name comes from
`ORCA_MSIX_STORE_PRODUCT_ID`) instead of the download flow. If the `GetCurrentPackageFamilyName` at runtime — no build-time ProductId
ProductId is not configured (pre-reservation builds), the menu action define or extra repo variable needed, and it works identically in
shows a message that updates are delivered through the Microsoft pre- and post-reservation builds. It never falls back to the classic
Store — it never falls back to the classic download flow. download flow.
**Association suppression (R3)** — the manifest owns shell integration; **Association suppression (R3)** — the manifest owns shell integration;
runtime registry writes are virtualized and invisible: runtime registry writes are virtualized and invisible:
- Early-return in `associate_files`, `disassociate_files`, - Early-return in `associate_files`, `disassociate_files`,
`associate_url`, `disassociate_url` when packaged. `associate_url`, `disassociate_url` when packaged.
- Hide the file-association checkboxes in Preferences - Hide the whole "Associate" tab in Preferences
(`Preferences.cpp:982+`) in packaged context. (`Preferences.cpp:1848-1883`) in packaged context — the
- Suppress any prompt/check that offers to (re)register associations file-association checkboxes and the `prusaslicer://` /
(e.g. `check_url_association` consumers) in packaged context — `bambustudio://` / `cura://` URL-handler rows all rely on runtime
implementation enumerates call sites. registry writes that are virtualized. The `check_url_association`
consumers live exclusively in that tab, so no other prompt suppression
is needed.
## What does NOT change ## What does NOT change
@@ -167,8 +173,8 @@ the PR documents manual verification per repo review guidelines:
The PR lands before Partner Center account approval — placeholder The PR lands before Partner Center account approval — placeholder
identity builds a valid artifact. Once the account clears and the app identity builds a valid artifact. Once the account clears and the app
name is reserved, set the three repo variables (identity name, publisher name is reserved, set the three repo variables (identity name, publisher
`CN=<GUID>`, ProductId); the next CI run produces the uploadable `CN=<GUID>`, publisher display name); the next CI run produces the
package. First Store submission is manual (listing, IARC, uploadable package. First Store submission is manual (listing, IARC,
`runFullTrust` + `unvirtualizedResources` justifications) per the `runFullTrust` + `unvirtualizedResources` justifications) per the
handoff doc's "Submission process" section. handoff doc's "Submission process" section.