From 158d6bc913411730545fe9ea10e8849eb042f171 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 12:12:36 +0800 Subject: [PATCH 1/8] fix: wire windows shortcut installer hook --- .../electron-builder-app-update.test.ts | 13 +++++++++++++ .../desktop-electron/electron-builder.config.ts | 4 ++++ packages/desktop-electron/resources/installer.nsh | 2 ++ 3 files changed, 19 insertions(+) create mode 100644 packages/desktop-electron/resources/installer.nsh diff --git a/packages/desktop-electron/electron-builder-app-update.test.ts b/packages/desktop-electron/electron-builder-app-update.test.ts index 556bb0aa3..2d01796b0 100644 --- a/packages/desktop-electron/electron-builder-app-update.test.ts +++ b/packages/desktop-electron/electron-builder-app-update.test.ts @@ -61,6 +61,19 @@ describe("electron builder app-update config", () => { }) }) + test("windows nsis installer uses PawWork shortcut customizations", () => { + const config = createConfig("prod") + + expect(config.nsis).toMatchObject({ + oneClick: false, + allowToChangeInstallationDirectory: true, + createDesktopShortcut: false, + createStartMenuShortcut: true, + include: "resources/installer.nsh", + installerLanguages: ["en_US", "zh_CN"], + }) + }) + test("all channels share the versioned artifact name", () => { expect(createConfig("dev").artifactName).toBe("pawwork-${os}-${arch}-${version}.${ext}") expect(createConfig("beta").artifactName).toBe("pawwork-${os}-${arch}-${version}.${ext}") diff --git a/packages/desktop-electron/electron-builder.config.ts b/packages/desktop-electron/electron-builder.config.ts index 40502a201..6e2821b32 100644 --- a/packages/desktop-electron/electron-builder.config.ts +++ b/packages/desktop-electron/electron-builder.config.ts @@ -160,6 +160,10 @@ const getBase = (): Configuration => ({ nsis: { oneClick: false, allowToChangeInstallationDirectory: true, + createDesktopShortcut: false, + createStartMenuShortcut: true, + include: "resources/installer.nsh", + installerLanguages: ["en_US", "zh_CN"], installerIcon: `resources/icons/icon.ico`, installerHeaderIcon: `resources/icons/icon.ico`, }, diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh new file mode 100644 index 000000000..aed77b277 --- /dev/null +++ b/packages/desktop-electron/resources/installer.nsh @@ -0,0 +1,2 @@ +!macro customInstallMode +!macroend From 6a40e1189f8565566c054d0d29ae7da5a84a257e Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 12:13:43 +0800 Subject: [PATCH 2/8] fix: add windows shortcut installer choice --- .../electron-builder-nsis-shortcut.test.ts | 54 +++++++++++ .../desktop-electron/resources/installer.nsh | 97 ++++++++++++++++++- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 packages/desktop-electron/electron-builder-nsis-shortcut.test.ts diff --git a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts new file mode 100644 index 000000000..79fb5b444 --- /dev/null +++ b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, test } from "bun:test" +import { readFileSync } from "node:fs" +import { join } from "node:path" + +const script = readFileSync(join(import.meta.dir, "resources", "installer.nsh"), "utf8") + +describe("windows nsis desktop shortcut customization", () => { + test("adds an assisted installer checkbox with English and Chinese labels", () => { + expect(script).toContain("AddDesktopShortcut") + expect(script).toContain("添加桌面快捷方式") + expect(script).toContain("Add desktop shortcut") + expect(script).toContain("BST_CHECKED") + }) + + test("uses language-aware standard shortcut names", () => { + expect(script).toContain("PawWork") + expect(script).toContain("爪印") + expect(script).toContain("$LANGUAGE") + }) + + test("does not mutate desktop shortcuts during auto-update", () => { + expect(script).toContain("${isUpdated}") + expect(script).toContain("PAWWORK_SKIP_DESKTOP_SHORTCUT") + }) + + test("keeps custom renamed shortcuts out of scope", () => { + expect(script).toContain("PAWWORK_STANDARD_SHORTCUT") + expect(script).not.toContain("我的 AI 工具") + }) + + test("declares a real custom page instead of running page commands inline", () => { + expect(script).toContain("PageEx custom") + expect(script).toContain("PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave") + expect(script).toContain('Function "PawWorkDesktopShortcutPageCreate"') + expect(script).toContain('Function "PawWorkDesktopShortcutPageLeave"') + }) + + test("uses channel-specific shortcut names instead of hard-coded prod names", () => { + expect(script).toContain("${SHORTCUT_NAME}") + expect(script).toContain('${If} "${SHORTCUT_NAME}" == "PawWork"') + expect(script).toContain('$PawWorkStandardShortcutName == "爪印"') + }) + + test("owns uninstall and cross-scope cleanup for standard shortcuts", () => { + expect(script).toContain("customUnInstall") + expect(script).toContain("PAWWORK_REMOVE_STANDARD_SHORTCUTS") + expect(script).toContain("SetShellVarContext current") + expect(script).toContain("SetShellVarContext all") + expect(script).toContain("PAWWORK_RESTORE_INSTALL_SCOPE") + expect(script).toContain( + "!insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES\n !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE", + ) + }) +}) diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh index aed77b277..c25f36e79 100644 --- a/packages/desktop-electron/resources/installer.nsh +++ b/packages/desktop-electron/resources/installer.nsh @@ -1,2 +1,97 @@ -!macro customInstallMode +!include nsDialogs.nsh +!include LogicLib.nsh + +Var AddDesktopShortcutCheckbox +Var AddDesktopShortcut +Var PawWorkStandardShortcutName + +LangString PawWorkAddDesktopShortcut ${LANG_ENGLISH} "Add desktop shortcut" +LangString PawWorkAddDesktopShortcut ${LANG_SIMPCHINESE} "添加桌面快捷方式" + +!macro PAWWORK_STANDARD_SHORTCUT + StrCpy $PawWorkStandardShortcutName "${SHORTCUT_NAME}" + ${If} $LANGUAGE == ${LANG_SIMPCHINESE} + ${AndIf} "${SHORTCUT_NAME}" == "PawWork" + StrCpy $PawWorkStandardShortcutName "爪印" + ${EndIf} +!macroend + +!macro PAWWORK_REMOVE_STANDARD_SHORTCUTS + !insertmacro PAWWORK_STANDARD_SHORTCUT + Delete "$DESKTOP\$PawWorkStandardShortcutName.lnk" + ${If} "${SHORTCUT_NAME}" == "PawWork" + Delete "$DESKTOP\PawWork.lnk" + Delete "$DESKTOP\爪印.lnk" + ${EndIf} +!macroend + +!macro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES + SetShellVarContext current + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS + SetShellVarContext all + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS +!macroend + +!macro PAWWORK_RESTORE_INSTALL_SCOPE + ${If} $installMode == "all" + SetShellVarContext all + ${Else} + SetShellVarContext current + ${EndIf} +!macroend + +!macro customPageAfterChangeDir + PageEx custom + PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave + Caption " " + PageExEnd +!macroend + +Function "PawWorkDesktopShortcutPageCreate" + ${If} ${isUpdated} + Abort + ${EndIf} + + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} + + ${NSD_CreateCheckbox} 0 0 100% 12u "$(PawWorkAddDesktopShortcut)" + Pop $AddDesktopShortcutCheckbox + ${NSD_Check} $AddDesktopShortcutCheckbox + nsDialogs::Show +FunctionEnd + +Function "PawWorkDesktopShortcutPageLeave" + StrCpy $AddDesktopShortcut "0" + ${NSD_GetState} $AddDesktopShortcutCheckbox $AddDesktopShortcut +FunctionEnd + +!macro customInstall + ${If} ${isUpdated} + StrCpy $AddDesktopShortcut "PAWWORK_SKIP_DESKTOP_SHORTCUT" + ${EndIf} + + ${If} $AddDesktopShortcut == ${BST_CHECKED} + !insertmacro PAWWORK_STANDARD_SHORTCUT + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES + !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE + ${If} "${SHORTCUT_NAME}" == "PawWork" + ${AndIf} $PawWorkStandardShortcutName == "爪印" + ${AndIf} ${FileExists} "$DESKTOP\PawWork.lnk" + Delete "$DESKTOP\PawWork.lnk" + ${EndIf} + CreateShortCut "$DESKTOP\$PawWorkStandardShortcutName.lnk" "$appExe" "" "$appExe" 0 "" "" "${APP_DESCRIPTION}" + ClearErrors + WinShell::SetLnkAUMI "$DESKTOP\$PawWorkStandardShortcutName.lnk" "${APP_ID}" + ${EndIf} +!macroend + +!macro customUnInstall + ${IfNot} ${isUpdated} + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES + !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE + ${EndIf} !macroend From 7b93fe96b4601a38a1c8ab8d3182e8e79b686962 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 12:14:19 +0800 Subject: [PATCH 3/8] docs: add windows shortcut release checks --- .github/RELEASE_CHECKLIST.md | 10 ++++++++++ .../scripts/release-workflow-contract.test.ts | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/.github/RELEASE_CHECKLIST.md b/.github/RELEASE_CHECKLIST.md index 191f5f792..091214c7f 100644 --- a/.github/RELEASE_CHECKLIST.md +++ b/.github/RELEASE_CHECKLIST.md @@ -214,6 +214,16 @@ try { } ``` +For Windows installer shortcut verification, record: + +- install mode: `Just me` or `All users` +- desktop shortcut checkbox state +- actual desktop shortcut result +- actual Start Menu result +- whether the desktop shortcut launches PawWork +- whether the Start Menu entry launches PawWork +- whether an update from the previous affected version leaves the existing desktop state unchanged + Keep `.zip`, `.blockmap`, and `latest*.yml` assets unless updater requirements are proven safe without them. If verification fails, check the reported missing or malformed asset first, rerun only the affected build phase, and publish the release only after the verification helper passes. diff --git a/packages/desktop-electron/scripts/release-workflow-contract.test.ts b/packages/desktop-electron/scripts/release-workflow-contract.test.ts index e7703206f..bccf7a53e 100644 --- a/packages/desktop-electron/scripts/release-workflow-contract.test.ts +++ b/packages/desktop-electron/scripts/release-workflow-contract.test.ts @@ -52,3 +52,14 @@ describe("release workflow app-update verification", () => { expect(workflow).toContain('officecli_platform="win32"') }) }) + +const checklist = readFileSync(join(import.meta.dir, "..", "..", "..", ".github", "RELEASE_CHECKLIST.md"), "utf8") + +describe("release checklist Windows installer verification", () => { + test("records desktop and Start Menu launch evidence", () => { + expect(checklist).toContain("desktop shortcut checkbox state") + expect(checklist).toContain("desktop shortcut launches PawWork") + expect(checklist).toContain("Start Menu entry launches PawWork") + expect(checklist).toContain("previous affected version") + }) +}) From 3249945fa167c0c6d618616ca13188f16b5c7f14 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 12:27:11 +0800 Subject: [PATCH 4/8] fix: compile windows shortcut installer script --- .../electron-builder-nsis-shortcut.test.ts | 15 ++- .../desktop-electron/resources/installer.nsh | 119 ++++++++++-------- 2 files changed, 78 insertions(+), 56 deletions(-) diff --git a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts index 79fb5b444..058eee96e 100644 --- a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts +++ b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts @@ -9,6 +9,10 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain("AddDesktopShortcut") expect(script).toContain("添加桌面快捷方式") expect(script).toContain("Add desktop shortcut") + expect(script).toContain('LangString PawWorkAddDesktopShortcut 1033 "Add desktop shortcut"') + expect(script).toContain('LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式"') + expect(script).not.toContain("LANG_ENGLISH") + expect(script).not.toContain("LANG_SIMPCHINESE") expect(script).toContain("BST_CHECKED") }) @@ -19,7 +23,10 @@ describe("windows nsis desktop shortcut customization", () => { }) test("does not mutate desktop shortcuts during auto-update", () => { - expect(script).toContain("${isUpdated}") + expect(script).toContain("!include FileFunc.nsh") + expect(script).toContain('"--updated"') + expect(script).not.toContain("${isUpdated}") + expect(script).not.toContain("!insertmacro skipPageIfUpdated") expect(script).toContain("PAWWORK_SKIP_DESKTOP_SHORTCUT") }) @@ -29,6 +36,8 @@ describe("windows nsis desktop shortcut customization", () => { }) test("declares a real custom page instead of running page commands inline", () => { + expect(script).toContain("!ifndef BUILD_UNINSTALLER") + expect(script).toContain("!ifndef BUILD_UNINSTALLER\n Var AddDesktopShortcutCheckbox") expect(script).toContain("PageEx custom") expect(script).toContain("PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave") expect(script).toContain('Function "PawWorkDesktopShortcutPageCreate"') @@ -47,8 +56,6 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain("SetShellVarContext current") expect(script).toContain("SetShellVarContext all") expect(script).toContain("PAWWORK_RESTORE_INSTALL_SCOPE") - expect(script).toContain( - "!insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES\n !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE", - ) + expect(script).toMatch(/PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES\s+!insertmacro PAWWORK_RESTORE_INSTALL_SCOPE/) }) }) diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh index c25f36e79..e8c96e801 100644 --- a/packages/desktop-electron/resources/installer.nsh +++ b/packages/desktop-electron/resources/installer.nsh @@ -1,16 +1,15 @@ !include nsDialogs.nsh !include LogicLib.nsh +!include FileFunc.nsh -Var AddDesktopShortcutCheckbox -Var AddDesktopShortcut Var PawWorkStandardShortcutName -LangString PawWorkAddDesktopShortcut ${LANG_ENGLISH} "Add desktop shortcut" -LangString PawWorkAddDesktopShortcut ${LANG_SIMPCHINESE} "添加桌面快捷方式" +LangString PawWorkAddDesktopShortcut 1033 "Add desktop shortcut" +LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" !macro PAWWORK_STANDARD_SHORTCUT StrCpy $PawWorkStandardShortcutName "${SHORTCUT_NAME}" - ${If} $LANGUAGE == ${LANG_SIMPCHINESE} + ${If} $LANGUAGE == 2052 ${AndIf} "${SHORTCUT_NAME}" == "PawWork" StrCpy $PawWorkStandardShortcutName "爪印" ${EndIf} @@ -40,58 +39,74 @@ LangString PawWorkAddDesktopShortcut ${LANG_SIMPCHINESE} "添加桌面快捷方 ${EndIf} !macroend -!macro customPageAfterChangeDir - PageEx custom - PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave - Caption " " - PageExEnd -!macroend +!ifndef BUILD_UNINSTALLER + Var AddDesktopShortcutCheckbox + Var AddDesktopShortcut -Function "PawWorkDesktopShortcutPageCreate" - ${If} ${isUpdated} - Abort - ${EndIf} + !macro customPageAfterChangeDir + PageEx custom + PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave + Caption " " + PageExEnd + !macroend - nsDialogs::Create 1018 - Pop $0 - ${If} $0 == error - Abort - ${EndIf} + Function "PawWorkDesktopShortcutPageCreate" + ClearErrors + ${GetParameters} $0 + ${GetOptions} $0 "--updated" $1 + ${IfNot} ${Errors} + Abort + ${EndIf} - ${NSD_CreateCheckbox} 0 0 100% 12u "$(PawWorkAddDesktopShortcut)" - Pop $AddDesktopShortcutCheckbox - ${NSD_Check} $AddDesktopShortcutCheckbox - nsDialogs::Show -FunctionEnd + nsDialogs::Create 1018 + Pop $0 + ${If} $0 == error + Abort + ${EndIf} -Function "PawWorkDesktopShortcutPageLeave" - StrCpy $AddDesktopShortcut "0" - ${NSD_GetState} $AddDesktopShortcutCheckbox $AddDesktopShortcut -FunctionEnd + ${NSD_CreateCheckbox} 0 0 100% 12u "$(PawWorkAddDesktopShortcut)" + Pop $AddDesktopShortcutCheckbox + ${NSD_Check} $AddDesktopShortcutCheckbox + nsDialogs::Show + FunctionEnd -!macro customInstall - ${If} ${isUpdated} - StrCpy $AddDesktopShortcut "PAWWORK_SKIP_DESKTOP_SHORTCUT" - ${EndIf} + Function "PawWorkDesktopShortcutPageLeave" + StrCpy $AddDesktopShortcut "0" + ${NSD_GetState} $AddDesktopShortcutCheckbox $AddDesktopShortcut + FunctionEnd - ${If} $AddDesktopShortcut == ${BST_CHECKED} - !insertmacro PAWWORK_STANDARD_SHORTCUT - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES - !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE - ${If} "${SHORTCUT_NAME}" == "PawWork" - ${AndIf} $PawWorkStandardShortcutName == "爪印" - ${AndIf} ${FileExists} "$DESKTOP\PawWork.lnk" - Delete "$DESKTOP\PawWork.lnk" - ${EndIf} - CreateShortCut "$DESKTOP\$PawWorkStandardShortcutName.lnk" "$appExe" "" "$appExe" 0 "" "" "${APP_DESCRIPTION}" + !macro customInstall ClearErrors - WinShell::SetLnkAUMI "$DESKTOP\$PawWorkStandardShortcutName.lnk" "${APP_ID}" - ${EndIf} -!macroend + ${GetParameters} $0 + ${GetOptions} $0 "--updated" $1 + ${IfNot} ${Errors} + StrCpy $AddDesktopShortcut "PAWWORK_SKIP_DESKTOP_SHORTCUT" + ${EndIf} -!macro customUnInstall - ${IfNot} ${isUpdated} - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES - !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE - ${EndIf} -!macroend + ${If} $AddDesktopShortcut == ${BST_CHECKED} + !insertmacro PAWWORK_STANDARD_SHORTCUT + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES + !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE + ${If} "${SHORTCUT_NAME}" == "PawWork" + ${AndIf} $PawWorkStandardShortcutName == "爪印" + ${AndIf} ${FileExists} "$DESKTOP\PawWork.lnk" + Delete "$DESKTOP\PawWork.lnk" + ${EndIf} + CreateShortCut "$DESKTOP\$PawWorkStandardShortcutName.lnk" "$appExe" "" "$appExe" 0 "" "" "${APP_DESCRIPTION}" + ClearErrors + WinShell::SetLnkAUMI "$DESKTOP\$PawWorkStandardShortcutName.lnk" "${APP_ID}" + ${EndIf} + !macroend +!endif + +!ifdef BUILD_UNINSTALLER + !macro customUnInstall + ClearErrors + ${GetParameters} $0 + ${GetOptions} $0 "--updated" $1 + ${If} ${Errors} + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES + !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE + ${EndIf} + !macroend +!endif From 051babc4bc00dc5bad82e8fd6a96fece244b69c9 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 12:41:01 +0800 Subject: [PATCH 5/8] fix: respect windows shortcut install scope --- .../electron-builder-nsis-shortcut.test.ts | 5 +++-- packages/desktop-electron/resources/installer.nsh | 11 ++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts index 058eee96e..97a1c5cca 100644 --- a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts +++ b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts @@ -50,12 +50,13 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain('$PawWorkStandardShortcutName == "爪印"') }) - test("owns uninstall and cross-scope cleanup for standard shortcuts", () => { + test("owns uninstall cleanup for standard shortcuts in the selected install scope", () => { expect(script).toContain("customUnInstall") expect(script).toContain("PAWWORK_REMOVE_STANDARD_SHORTCUTS") + expect(script).not.toContain("PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES") expect(script).toContain("SetShellVarContext current") expect(script).toContain("SetShellVarContext all") expect(script).toContain("PAWWORK_RESTORE_INSTALL_SCOPE") - expect(script).toMatch(/PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES\s+!insertmacro PAWWORK_RESTORE_INSTALL_SCOPE/) + expect(script).toMatch(/PAWWORK_RESTORE_INSTALL_SCOPE\s+!insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS/) }) }) diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh index e8c96e801..97ac919d7 100644 --- a/packages/desktop-electron/resources/installer.nsh +++ b/packages/desktop-electron/resources/installer.nsh @@ -24,13 +24,6 @@ LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" ${EndIf} !macroend -!macro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES - SetShellVarContext current - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS - SetShellVarContext all - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS -!macroend - !macro PAWWORK_RESTORE_INSTALL_SCOPE ${If} $installMode == "all" SetShellVarContext all @@ -85,8 +78,8 @@ LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" ${If} $AddDesktopShortcut == ${BST_CHECKED} !insertmacro PAWWORK_STANDARD_SHORTCUT - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS ${If} "${SHORTCUT_NAME}" == "PawWork" ${AndIf} $PawWorkStandardShortcutName == "爪印" ${AndIf} ${FileExists} "$DESKTOP\PawWork.lnk" @@ -105,8 +98,8 @@ LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" ${GetParameters} $0 ${GetOptions} $0 "--updated" $1 ${If} ${Errors} - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_BOTH_SCOPES !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS ${EndIf} !macroend !endif From 99935eb36418c2a3513f485b95e4e4b4e261469c Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 13:20:14 +0800 Subject: [PATCH 6/8] fix: address windows shortcut review notes --- .github/RELEASE_CHECKLIST.md | 24 ++++++++++++------- .../electron-builder-nsis-shortcut.test.ts | 13 ++++++++-- .../desktop-electron/resources/installer.nsh | 20 +++++++++------- .../scripts/release-workflow-contract.test.ts | 15 ++++++++---- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/.github/RELEASE_CHECKLIST.md b/.github/RELEASE_CHECKLIST.md index 091214c7f..116fefd94 100644 --- a/.github/RELEASE_CHECKLIST.md +++ b/.github/RELEASE_CHECKLIST.md @@ -214,15 +214,21 @@ try { } ``` -For Windows installer shortcut verification, record: - -- install mode: `Just me` or `All users` -- desktop shortcut checkbox state -- actual desktop shortcut result -- actual Start Menu result -- whether the desktop shortcut launches PawWork -- whether the Start Menu entry launches PawWork -- whether an update from the previous affected version leaves the existing desktop state unchanged +For Windows installer shortcut verification, record the minimum matrix: + +- English Windows fresh install, `Just me`, desktop shortcut checked: current user desktop shortcut exists and launches PawWork +- English Windows fresh install, `All users`, desktop shortcut checked: public desktop shortcut exists and launches PawWork +- Chinese Windows fresh install, `Just me`, desktop shortcut checked: current user desktop shortcut is `爪印.lnk` and launches PawWork +- Chinese Windows fresh install, `All users`, desktop shortcut checked: public desktop shortcut is `爪印.lnk` and launches PawWork +- unchecked install: no desktop shortcut is created and the Start Menu entry still launches PawWork +- reinstall with desktop shortcut checked: missing standard desktop shortcut is repaired +- reinstall with desktop shortcut unchecked: existing desktop shortcut state is left unchanged +- scope switch between `Just me` and `All users`: standard desktop shortcut only exists in the selected install scope +- Chinese reinstall over an older standard `PawWork.lnk`: standard desktop shortcut migrates to `爪印.lnk` +- app language change after install: desktop shortcut name is not changed +- auto-update from the previous affected version: existing desktop shortcut state is left unchanged, including the no-desktop-shortcut state + +Do not close the Windows desktop shortcut issue until this real Windows installer evidence is recorded. Keep `.zip`, `.blockmap`, and `latest*.yml` assets unless updater requirements are proven safe without them. diff --git a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts index 97a1c5cca..4e0353491 100644 --- a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts +++ b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts @@ -9,8 +9,12 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain("AddDesktopShortcut") expect(script).toContain("添加桌面快捷方式") expect(script).toContain("Add desktop shortcut") + expect(script).toContain("Shortcut Options") + expect(script).toContain("快捷方式选项") expect(script).toContain('LangString PawWorkAddDesktopShortcut 1033 "Add desktop shortcut"') expect(script).toContain('LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式"') + expect(script).toContain('LangString PawWorkShortcutOptions 1033 "Shortcut Options"') + expect(script).toContain('LangString PawWorkShortcutOptions 2052 "快捷方式选项"') expect(script).not.toContain("LANG_ENGLISH") expect(script).not.toContain("LANG_SIMPCHINESE") expect(script).toContain("BST_CHECKED") @@ -19,6 +23,8 @@ describe("windows nsis desktop shortcut customization", () => { test("uses language-aware standard shortcut names", () => { expect(script).toContain("PawWork") expect(script).toContain("爪印") + expect(script).toContain("爪印 Beta") + expect(script).toContain("爪印 Dev") expect(script).toContain("$LANGUAGE") }) @@ -39,6 +45,7 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain("!ifndef BUILD_UNINSTALLER") expect(script).toContain("!ifndef BUILD_UNINSTALLER\n Var AddDesktopShortcutCheckbox") expect(script).toContain("PageEx custom") + expect(script).toContain('Caption "$(PawWorkShortcutOptions)"') expect(script).toContain("PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave") expect(script).toContain('Function "PawWorkDesktopShortcutPageCreate"') expect(script).toContain('Function "PawWorkDesktopShortcutPageLeave"') @@ -46,8 +53,10 @@ describe("windows nsis desktop shortcut customization", () => { test("uses channel-specific shortcut names instead of hard-coded prod names", () => { expect(script).toContain("${SHORTCUT_NAME}") - expect(script).toContain('${If} "${SHORTCUT_NAME}" == "PawWork"') - expect(script).toContain('$PawWorkStandardShortcutName == "爪印"') + expect(script).toContain('${AndIf} "${SHORTCUT_NAME}" == "PawWork"') + expect(script).toContain('${AndIf} "${SHORTCUT_NAME}" == "PawWork Beta"') + expect(script).toContain('${AndIf} "${SHORTCUT_NAME}" == "PawWork Dev"') + expect(script).toContain('Delete "$DESKTOP\\${SHORTCUT_NAME}.lnk"') }) test("owns uninstall cleanup for standard shortcuts in the selected install scope", () => { diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh index 97ac919d7..a7329c0bf 100644 --- a/packages/desktop-electron/resources/installer.nsh +++ b/packages/desktop-electron/resources/installer.nsh @@ -6,21 +6,28 @@ Var PawWorkStandardShortcutName LangString PawWorkAddDesktopShortcut 1033 "Add desktop shortcut" LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" +LangString PawWorkShortcutOptions 1033 "Shortcut Options" +LangString PawWorkShortcutOptions 2052 "快捷方式选项" !macro PAWWORK_STANDARD_SHORTCUT StrCpy $PawWorkStandardShortcutName "${SHORTCUT_NAME}" ${If} $LANGUAGE == 2052 ${AndIf} "${SHORTCUT_NAME}" == "PawWork" StrCpy $PawWorkStandardShortcutName "爪印" + ${ElseIf} $LANGUAGE == 2052 + ${AndIf} "${SHORTCUT_NAME}" == "PawWork Beta" + StrCpy $PawWorkStandardShortcutName "爪印 Beta" + ${ElseIf} $LANGUAGE == 2052 + ${AndIf} "${SHORTCUT_NAME}" == "PawWork Dev" + StrCpy $PawWorkStandardShortcutName "爪印 Dev" ${EndIf} !macroend !macro PAWWORK_REMOVE_STANDARD_SHORTCUTS !insertmacro PAWWORK_STANDARD_SHORTCUT Delete "$DESKTOP\$PawWorkStandardShortcutName.lnk" - ${If} "${SHORTCUT_NAME}" == "PawWork" - Delete "$DESKTOP\PawWork.lnk" - Delete "$DESKTOP\爪印.lnk" + ${If} $PawWorkStandardShortcutName != "${SHORTCUT_NAME}" + Delete "$DESKTOP\${SHORTCUT_NAME}.lnk" ${EndIf} !macroend @@ -39,7 +46,7 @@ LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" !macro customPageAfterChangeDir PageEx custom PageCallbacks PawWorkDesktopShortcutPageCreate PawWorkDesktopShortcutPageLeave - Caption " " + Caption "$(PawWorkShortcutOptions)" PageExEnd !macroend @@ -80,11 +87,6 @@ LangString PawWorkAddDesktopShortcut 2052 "添加桌面快捷方式" !insertmacro PAWWORK_STANDARD_SHORTCUT !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS - ${If} "${SHORTCUT_NAME}" == "PawWork" - ${AndIf} $PawWorkStandardShortcutName == "爪印" - ${AndIf} ${FileExists} "$DESKTOP\PawWork.lnk" - Delete "$DESKTOP\PawWork.lnk" - ${EndIf} CreateShortCut "$DESKTOP\$PawWorkStandardShortcutName.lnk" "$appExe" "" "$appExe" 0 "" "" "${APP_DESCRIPTION}" ClearErrors WinShell::SetLnkAUMI "$DESKTOP\$PawWorkStandardShortcutName.lnk" "${APP_ID}" diff --git a/packages/desktop-electron/scripts/release-workflow-contract.test.ts b/packages/desktop-electron/scripts/release-workflow-contract.test.ts index bccf7a53e..f9d8ec63b 100644 --- a/packages/desktop-electron/scripts/release-workflow-contract.test.ts +++ b/packages/desktop-electron/scripts/release-workflow-contract.test.ts @@ -56,10 +56,17 @@ describe("release workflow app-update verification", () => { const checklist = readFileSync(join(import.meta.dir, "..", "..", "..", ".github", "RELEASE_CHECKLIST.md"), "utf8") describe("release checklist Windows installer verification", () => { - test("records desktop and Start Menu launch evidence", () => { - expect(checklist).toContain("desktop shortcut checkbox state") - expect(checklist).toContain("desktop shortcut launches PawWork") - expect(checklist).toContain("Start Menu entry launches PawWork") + test("records the Windows desktop shortcut verification matrix", () => { + expect(checklist).toContain("English Windows fresh install") + expect(checklist).toContain("Chinese Windows fresh install") + expect(checklist).toContain("unchecked install") + expect(checklist).toContain("reinstall with desktop shortcut checked") + expect(checklist).toContain("reinstall with desktop shortcut unchecked") + expect(checklist).toContain("scope switch between `Just me` and `All users`") + expect(checklist).toContain("older standard `PawWork.lnk`") + expect(checklist).toContain("app language change after install") expect(checklist).toContain("previous affected version") + expect(checklist).toContain("no-desktop-shortcut state") + expect(checklist).toContain("Do not close the Windows desktop shortcut issue") }) }) From 634243555cbf7f9133f2bb697ca8fce5d8565e24 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 13:44:05 +0800 Subject: [PATCH 7/8] fix: clean shortcut scope switch leftovers --- .../electron-builder-nsis-shortcut.test.ts | 7 +++++++ packages/desktop-electron/resources/installer.nsh | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts index 4e0353491..20c9ca768 100644 --- a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts +++ b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts @@ -59,6 +59,13 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain('Delete "$DESKTOP\\${SHORTCUT_NAME}.lnk"') }) + test("cleans standard shortcuts across scopes only for checked installs", () => { + expect(script).toContain("PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_ALL_INSTALL_SCOPES") + expect(script).toMatch( + /\$AddDesktopShortcut == \$\{BST_CHECKED\}[\s\S]*PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_ALL_INSTALL_SCOPES[\s\S]*PAWWORK_RESTORE_INSTALL_SCOPE[\s\S]*CreateShortCut/, + ) + }) + test("owns uninstall cleanup for standard shortcuts in the selected install scope", () => { expect(script).toContain("customUnInstall") expect(script).toContain("PAWWORK_REMOVE_STANDARD_SHORTCUTS") diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh index a7329c0bf..9699c3e98 100644 --- a/packages/desktop-electron/resources/installer.nsh +++ b/packages/desktop-electron/resources/installer.nsh @@ -31,6 +31,13 @@ LangString PawWorkShortcutOptions 2052 "快捷方式选项" ${EndIf} !macroend +!macro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_ALL_INSTALL_SCOPES + SetShellVarContext current + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS + SetShellVarContext all + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS +!macroend + !macro PAWWORK_RESTORE_INSTALL_SCOPE ${If} $installMode == "all" SetShellVarContext all @@ -85,8 +92,8 @@ LangString PawWorkShortcutOptions 2052 "快捷方式选项" ${If} $AddDesktopShortcut == ${BST_CHECKED} !insertmacro PAWWORK_STANDARD_SHORTCUT + !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS_IN_ALL_INSTALL_SCOPES !insertmacro PAWWORK_RESTORE_INSTALL_SCOPE - !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS CreateShortCut "$DESKTOP\$PawWorkStandardShortcutName.lnk" "$appExe" "" "$appExe" 0 "" "" "${APP_DESCRIPTION}" ClearErrors WinShell::SetLnkAUMI "$DESKTOP\$PawWorkStandardShortcutName.lnk" "${APP_ID}" From 5d08f68a9df3b5023a24fc355a11eb09cb195ddc Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Fri, 8 May 2026 14:08:00 +0800 Subject: [PATCH 8/8] fix: elevate public shortcut cleanup --- .../electron-builder-nsis-shortcut.test.ts | 11 +++++++ .../desktop-electron/resources/installer.nsh | 31 +++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts index 20c9ca768..6abf6d45b 100644 --- a/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts +++ b/packages/desktop-electron/electron-builder-nsis-shortcut.test.ts @@ -57,6 +57,9 @@ describe("windows nsis desktop shortcut customization", () => { expect(script).toContain('${AndIf} "${SHORTCUT_NAME}" == "PawWork Beta"') expect(script).toContain('${AndIf} "${SHORTCUT_NAME}" == "PawWork Dev"') expect(script).toContain('Delete "$DESKTOP\\${SHORTCUT_NAME}.lnk"') + expect(script).toContain('Delete "$DESKTOP\\爪印.lnk"') + expect(script).toContain('Delete "$DESKTOP\\爪印 Beta.lnk"') + expect(script).toContain('Delete "$DESKTOP\\爪印 Dev.lnk"') }) test("cleans standard shortcuts across scopes only for checked installs", () => { @@ -66,6 +69,14 @@ describe("windows nsis desktop shortcut customization", () => { ) }) + test("uses an elevated public desktop cleanup when switching from all-users to just-me", () => { + expect(script).toContain("PAWWORK_REMOVE_PUBLIC_STANDARD_SHORTCUTS_ELEVATED") + expect(script).toContain('${IfNot} ${UAC_IsAdmin}') + expect(script).toContain('$installMode != "all"') + expect(script).toContain('$hasPerMachineInstallation == "1"') + expect(script).toContain('${StdUtils.ExecShellWaitEx} $0 $1 "$SYSDIR\\cmd.exe" "runas"') + }) + test("owns uninstall cleanup for standard shortcuts in the selected install scope", () => { expect(script).toContain("customUnInstall") expect(script).toContain("PAWWORK_REMOVE_STANDARD_SHORTCUTS") diff --git a/packages/desktop-electron/resources/installer.nsh b/packages/desktop-electron/resources/installer.nsh index 9699c3e98..8583c07a9 100644 --- a/packages/desktop-electron/resources/installer.nsh +++ b/packages/desktop-electron/resources/installer.nsh @@ -1,6 +1,8 @@ !include nsDialogs.nsh !include LogicLib.nsh !include FileFunc.nsh +!include StdUtils.nsh +!include UAC.nsh Var PawWorkStandardShortcutName @@ -26,8 +28,32 @@ LangString PawWorkShortcutOptions 2052 "快捷方式选项" !macro PAWWORK_REMOVE_STANDARD_SHORTCUTS !insertmacro PAWWORK_STANDARD_SHORTCUT Delete "$DESKTOP\$PawWorkStandardShortcutName.lnk" - ${If} $PawWorkStandardShortcutName != "${SHORTCUT_NAME}" - Delete "$DESKTOP\${SHORTCUT_NAME}.lnk" + Delete "$DESKTOP\${SHORTCUT_NAME}.lnk" + ${If} "${SHORTCUT_NAME}" == "PawWork" + Delete "$DESKTOP\PawWork.lnk" + Delete "$DESKTOP\爪印.lnk" + ${ElseIf} "${SHORTCUT_NAME}" == "PawWork Beta" + Delete "$DESKTOP\PawWork Beta.lnk" + Delete "$DESKTOP\爪印 Beta.lnk" + ${ElseIf} "${SHORTCUT_NAME}" == "PawWork Dev" + Delete "$DESKTOP\PawWork Dev.lnk" + Delete "$DESKTOP\爪印 Dev.lnk" + ${EndIf} +!macroend + +!macro PAWWORK_REMOVE_PUBLIC_STANDARD_SHORTCUTS_ELEVATED + # A per-user reinstall cannot remove Public Desktop shortcuts without elevation. + ${IfNot} ${UAC_IsAdmin} + ${AndIf} $installMode != "all" + ${AndIf} $hasPerMachineInstallation == "1" + SetShellVarContext all + ${If} "${SHORTCUT_NAME}" == "PawWork" + ${StdUtils.ExecShellWaitEx} $0 $1 "$SYSDIR\cmd.exe" "runas" '/C del /F /Q "$DESKTOP\PawWork.lnk" "$DESKTOP\爪印.lnk"' + ${ElseIf} "${SHORTCUT_NAME}" == "PawWork Beta" + ${StdUtils.ExecShellWaitEx} $0 $1 "$SYSDIR\cmd.exe" "runas" '/C del /F /Q "$DESKTOP\PawWork Beta.lnk" "$DESKTOP\爪印 Beta.lnk"' + ${ElseIf} "${SHORTCUT_NAME}" == "PawWork Dev" + ${StdUtils.ExecShellWaitEx} $0 $1 "$SYSDIR\cmd.exe" "runas" '/C del /F /Q "$DESKTOP\PawWork Dev.lnk" "$DESKTOP\爪印 Dev.lnk"' + ${EndIf} ${EndIf} !macroend @@ -36,6 +62,7 @@ LangString PawWorkShortcutOptions 2052 "快捷方式选项" !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS SetShellVarContext all !insertmacro PAWWORK_REMOVE_STANDARD_SHORTCUTS + !insertmacro PAWWORK_REMOVE_PUBLIC_STANDARD_SHORTCUTS_ELEVATED !macroend !macro PAWWORK_RESTORE_INSTALL_SCOPE