From 7f203bc1c8d472840eaf97b1d900c9bd61b87c46 Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Mon, 19 Oct 2026 01:49:43 +0100 Subject: [PATCH 1/2] #39: per-user Windows installer that needs no administrator rights Build wordcraft.wxs a second time as a dual-purpose package (Scope=perUserOrMachine: ALLUSERS=2, MSIINSTALLPERUSER=1), shipped as wordcraft--windows-user-.msi. Registry keys move from HKLM to HKMU so the same components land in HKLM per-machine and HKCU per-user. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 14 +++-- ROADMAP.md | 1 + docs/releasing.md | 2 +- packaging/windows/package.ps1 | 31 ++++++---- packaging/windows/wordcraft.wxs | 104 +++++++++++++++++--------------- 5 files changed, 87 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 42340c3133..b52cf3e518 100644 --- a/README.md +++ b/README.md @@ -221,13 +221,15 @@ closer pagination fidelity, a stability sweep and native printing. Details and e ### Windows -| Build | Installer | Portable | -|---|---|---| -| x64 (64-bit Intel/AMD) | `wordcraft--windows-x64.msi` | `wordcraft--windows-x64-portable.zip` | -| arm64 (Snapdragon and other ARM PCs) | `wordcraft--windows-arm64.msi` | `wordcraft--windows-arm64-portable.zip` | -| x86 (32-bit) | `wordcraft--windows-x86.msi` | `wordcraft--windows-x86-portable.zip` | +| Build | Installer (all users) | Installer (just me) | Portable | +|---|---|---|---| +| x64 (64-bit Intel/AMD) | `wordcraft--windows-x64.msi` | `wordcraft--windows-user-x64.msi` | `wordcraft--windows-x64-portable.zip` | +| arm64 (Snapdragon and other ARM PCs) | `wordcraft--windows-arm64.msi` | `wordcraft--windows-user-arm64.msi` | `wordcraft--windows-arm64-portable.zip` | +| x86 (32-bit) | `wordcraft--windows-x86.msi` | `wordcraft--windows-user-x86.msi` | `wordcraft--windows-x86-portable.zip` | -Installers and executables are code-signed. +The all-users installer puts WordCraft in Program Files and needs administrator rights. The +just-me installer needs none: it installs into `%LOCALAPPDATA%\Programs\WordCraft` for your +account only. Installers and executables are code-signed. **If the app doesn't open on Windows:** the desktop app initializes only DirectX 12 by default. Letting wgpu also create an OpenGL instance can crash some graphics drivers (AMD's diff --git a/ROADMAP.md b/ROADMAP.md index 2d0b45ddd2..9072433bdb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -210,6 +210,7 @@ Imported Word 97–2003 `.doc`/`.dot` files now ask for a supported DOCX target | 2026-10-20 | Restrict Editing holds for the file commands too: the privacy options, the properties and the inspector's removals are refused on a protected document, and restoring a stored version keeps the protection and its password instead of the snapshot's stale ones | | 2026-10-17 | Ribbon hints' reasons for a greyed-out control are translated in every interface language; a test fails if a reason it finds in the command sources, or the "Open a document first" hint, has no translation (#2678). | | 2026-10-17 | Ribbon hover hints: a disabled control says why (the command's own reason), split-button arrows, menu buttons and the font, size and outline boxes have hints, and hints show on keyboard focus (#1086). | +| 2026-10-16 | Windows per-user installer (#39): each release also ships `wordcraft--windows-user-.msi`, built from the same WiX source as a dual-purpose package, so it installs into `%LOCALAPPDATA%\Programs` for the current user with no administrator rights; registry keys moved to HKMU so both installers share one set of components | | 2026-10-14 | DOCX row table border exceptions render and survive saving. Explicit cell borders win; a row exception does not change other rows. | | 2026-10-14 | Tab stops edited on the ruler: the corner selector picks Left/Center/Right/Decimal/Bar, a click sets a stop (Alt skips the snap), dragging moves it with a guide line and position tooltip, dragging off the ruler clears it; markers draw per kind and mirror for RTL; one `para.tabs` call and one undo step per gesture | | 2026-10-12 | Fields keep `w:dirty` through DOCX saves (#877): a begin marker flagged by a generator that couldn't compute a result (pandoc-style TOC placeholders) keeps the flag on open/save — as a buffered field or a range field — so Word still refreshes it on open; computing a result (Update Field, Update Table) clears it, like Word's F9 | diff --git a/docs/releasing.md b/docs/releasing.md index 411af7593e..fc2503550a 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -11,7 +11,7 @@ this file has the WordCraft specifics. | Platform | Artifacts | Signing | |---|---|---| | macOS | universal `WordCraft.app` in a `.dmg`, universal CLI zip | Developer ID + notarization (`APPLE_*` secrets) | -| Windows | x64, x86 and arm64 `.msi` + portable `.zip` | Azure Trusted Signing (`AZURE_*` secrets) | +| Windows | x64, x86 and arm64 `.msi` (per-machine, and a per-user `-windows-user-.msi` that needs no admin rights) + portable `.zip` | Azure Trusted Signing (`AZURE_*` secrets) | | Linux | x86_64 and aarch64 `.AppImage` (+ `.zsync` for AppImageUpdate), `.flatpak` bundle, `.deb`, `.rpm`, `.tar.gz`; Flathub manifest | checksums | | Linux riscv64 | `.tar.gz` (cross-compiled on `ubuntu-24.04`, glibc >= 2.39; CLI smoke-tested under QEMU) | checksums | | FreeBSD | x86_64 `.tar.gz` | checksums | diff --git a/packaging/windows/package.ps1 b/packaging/windows/package.ps1 index 5223527598..e89a25e432 100644 --- a/packaging/windows/package.ps1 +++ b/packaging/windows/package.ps1 @@ -5,6 +5,7 @@ .DESCRIPTION Produces, in $env:DIST (default: dist/release): wordcraft--windows-.msi per-machine installer (WiX v5) + wordcraft--windows-user-.msi per-user installer, no admin rights (#39) wordcraft--windows--portable.zip wordcraft.exe + wordcraft-cli.exe The binaries link the C runtime statically (+crt-static), so neither the MSI nor the portable @@ -142,18 +143,26 @@ $rtf = '{\rtf1\ansi\ansicpg1252\deff0{\fonttbl{\f0\fswiss\fcharset0 Tahoma;}}\vi ($paragraphs -join "\par`r`n") + "\par`r`n}`r`n" [System.IO.File]::WriteAllText($LicenseRtf, $rtf, [System.Text.Encoding]::ASCII) +# Two MSIs from one source: per-machine (Program Files, needs admin) and per-user (#39: no admin, +# into %LOCALAPPDATA%\Programs; see wordcraft.wxs). The per-user name puts "user" before the arch +# so the per-machine MSI sorts first: the shared windows-arm64 workflow installs the first *.msi +# it finds and checks Program Files. $Msi = Join-Path $Dist "wordcraft-$Version-windows-$Arch.msi" -Invoke-Native 'wix build' { - wix build (Join-Path $PSScriptRoot 'wordcraft.wxs') -arch $Arch ` - -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext ` - -d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\wordcraft.ico')" ` - -d "LicenseRtf=$LicenseRtf" ` - -d "AppNoticesDir=$AppNotices" -d "DocsDir=$Docs" -d "HasCraftFontLicences=$HasCraftFontLicences" ` - -o $Msi +$UserMsi = Join-Path $Dist "wordcraft-$Version-windows-user-$Arch.msi" +foreach ($build in @(@($Msi, 'perMachine'), @($UserMsi, 'perUserOrMachine'))) { + $out = $build[0] + Invoke-Native "wix build ($($build[1]))" { + wix build (Join-Path $PSScriptRoot 'wordcraft.wxs') -arch $Arch ` + -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext ` + -d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\wordcraft.ico')" ` + -d "LicenseRtf=$LicenseRtf" ` + -d "AppNoticesDir=$AppNotices" -d "DocsDir=$Docs" -d "HasCraftFontLicences=$HasCraftFontLicences" -d "Scope=$($build[1])" ` + -o $out + } + # wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets. + Remove-Item -Force -ErrorAction SilentlyContinue ([IO.Path]::ChangeExtension($out, '.wixpdb')) + & (Join-Path $PSScriptRoot 'sign.ps1') $out } -# wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets. -Remove-Item -Force -ErrorAction SilentlyContinue ([IO.Path]::ChangeExtension($Msi, '.wixpdb')) -& (Join-Path $PSScriptRoot 'sign.ps1') $Msi # ---- portable zip ------------------------------------------------------------------------------ $Portable = Join-Path $TargetDir "windows-package\wordcraft-$Version-windows-$Arch-portable" @@ -182,4 +191,4 @@ if ($Arch -ne 'arm64' -or $HostArch -eq 'arm64') { } else { Write-Output "skipping wordcraft-cli --version: an $Arch build doesn't run on this $HostArch machine" } -Get-Item $Msi, $Zip | Format-Table Name, Length +Get-Item $Msi, $UserMsi, $Zip | Format-Table Name, Length diff --git a/packaging/windows/wordcraft.wxs b/packaging/windows/wordcraft.wxs index 27aa89ca25..5be96705b1 100644 --- a/packaging/windows/wordcraft.wxs +++ b/packaging/windows/wordcraft.wxs @@ -5,16 +5,26 @@ Built by package.ps1: wix build wordcraft.wxs -arch x64 -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext - -d Version=1.2.3 -d BinDir=... -d IconPath=... -d LicenseRtf=... -o out.msi + -d Version=1.2.3 -d BinDir=... -d IconPath=... -d LicenseRtf=... [-d Scope=perUserOrMachine] -o out.msi Per-machine install into Program Files with a Start Menu shortcut. The installer shows the standard WiX dialogs: welcome + license, progress, and a finish page. + Built a second time with -d Scope=perUserOrMachine for people without administrator rights + (#39): a dual-purpose package that WiX gives ALLUSERS=2 and MSIINSTALLPERUSER=1, so it installs + for the current user with no UAC prompt. Windows then redirects Program Files to + %LOCALAPPDATA%\Programs, the Start Menu to the user's own, and HKMU to HKCU, so the same + components work in both packages. Both share the UpgradeCode: each upgrades its own earlier + versions (Windows Installer only matches products installed in the same context). + UpgradeCode and the component GUIDs below belong to WordCraft alone. Never copy them from (or into) a sibling Craft app: Windows Installer treats every package sharing an UpgradeCode as one product, so a shared code makes one app's installer refuse to install ("a newer version is already installed") or silently uninstall the other. packaging-lint.yml checks this. --> + + + @@ -24,7 +34,7 @@ Manufacturer="Learning Machines LLC" Version="$(var.Version)" UpgradeCode="075497F9-E861-45D0-9C7F-A74F8C34E6BA" - Scope="perMachine" + Scope="$(var.Scope)" Compressed="yes" Language="1033"> @@ -88,7 +98,7 @@ Icon="WordcraftIcon.ico" IconIndex="0" /> - + - - - - - - + + + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - + + + - - - + + + From c1eb02f23c22106731c5e2d0a5c22fc73effe25c Mon Sep 17 00:00:00 2001 From: Martin Ball Date: Mon, 19 Oct 2026 01:51:22 +0100 Subject: [PATCH 2/2] #39: README: installing both Windows installers gives two entries Review follow-up (FredApps): the all-users and just-me MSIs install side by side, so pick one. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b52cf3e518..b0445a01eb 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,8 @@ closer pagination fidelity, a stability sweep and native printing. Details and e The all-users installer puts WordCraft in Program Files and needs administrator rights. The just-me installer needs none: it installs into `%LOCALAPPDATA%\Programs\WordCraft` for your -account only. Installers and executables are code-signed. +account only. Choose one: installing both gives two WordCraft entries in Settings › Apps and two +in the Start menu. Installers and executables are code-signed. **If the app doesn't open on Windows:** the desktop app initializes only DirectX 12 by default. Letting wgpu also create an OpenGL instance can crash some graphics drivers (AMD's