From cfee127d8d721ea76950f4da55dacbfd73afe50c Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:40:32 +0800 Subject: [PATCH 1/8] fix windows protocol handler path --- windows/installer/codex-dream-skin.iss | 7 ++++--- windows/tests/installer-static.tests.ps1 | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/windows/installer/codex-dream-skin.iss b/windows/installer/codex-dream-skin.iss index 798e3a3f..932f6c7a 100644 --- a/windows/installer/codex-dream-skin.iss +++ b/windows/installer/codex-dream-skin.iss @@ -12,6 +12,7 @@ #define AppPublisher "Codex Dream Skin contributors" #define AppUrl "https://dreamskin.cc" #define PowerShellPath "{sysnative}\WindowsPowerShell\v1.0\powershell.exe" +#define PersistentPowerShellPath "{win}\System32\WindowsPowerShell\v1.0\powershell.exe" [Setup] AppId={{DCCDAF1A-9ACD-4AAB-B55B-DF17EB2CDA2E} @@ -72,14 +73,14 @@ Source: "{#StageRoot}\NOTICE.md"; DestDir: "{app}"; Flags: ignoreversion Source: "{#StageRoot}\payload\*"; DestDir: "{app}\payload"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] -Name: "{group}\Codex Dream Skin"; Filename: "{#PowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico" -Name: "{userstartup}\Codex Dream Skin"; Filename: "{#PowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico"; Tasks: startup +Name: "{group}\Codex Dream Skin"; Filename: "{#PersistentPowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico" +Name: "{userstartup}\Codex Dream Skin"; Filename: "{#PersistentPowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; IconFilename: "{app}\payload\assets\codex-dream-skin.ico"; Tasks: startup [Registry] Root: HKCU; Subkey: "Software\Classes\dreamskin"; ValueType: string; ValueName: ""; ValueData: "URL:DreamSkin Protocol"; Flags: uninsdeletekey Root: HKCU; Subkey: "Software\Classes\dreamskin"; ValueType: string; ValueName: "URL Protocol"; ValueData: "" Root: HKCU; Subkey: "Software\Classes\dreamskin\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\payload\assets\codex-dream-skin.ico" -Root: HKCU; Subkey: "Software\Classes\dreamskin\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{#PowerShellPath}"" -NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{localappdata}\CodexDreamSkin\engine\scripts\apply-community-theme.ps1"" ""%1""" +Root: HKCU; Subkey: "Software\Classes\dreamskin\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{#PersistentPowerShellPath}"" -NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{localappdata}\CodexDreamSkin\engine\scripts\apply-community-theme.ps1"" ""%1""" [Run] Filename: "{#PowerShellPath}"; Parameters: "-NoProfile -STA -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File ""{app}\setup-bootstrap.ps1"" -LaunchTray"; WorkingDir: "{app}"; Description: "Launch Codex Dream Skin"; Flags: nowait postinstall skipifsilent diff --git a/windows/tests/installer-static.tests.ps1 b/windows/tests/installer-static.tests.ps1 index bb0060e9..ca0e7a39 100644 --- a/windows/tests/installer-static.tests.ps1 +++ b/windows/tests/installer-static.tests.ps1 @@ -93,6 +93,26 @@ if ($definition.Contains('Root: HKLM') -or [regex]::Matches($definition, '(?m)^Root: HKCU; Subkey: "Software\\Classes\\dreamskin').Count -ne 4) { throw 'The dreamskin protocol must be a four-entry current-user registration with no arbitrary URL contract.' } +if (-not $definition.Contains('#define PersistentPowerShellPath "{win}\System32\WindowsPowerShell\v1.0\powershell.exe"')) { + throw 'Persistent shortcuts and URL handlers must use a System32 PowerShell path that 64-bit launchers can access.' +} +$persistentCommandEntries = @( + 'Name: "{group}\Codex Dream Skin"', + 'Name: "{userstartup}\Codex Dream Skin"', + 'Subkey: "Software\Classes\dreamskin\shell\open\command"' +) +foreach ($entry in $persistentCommandEntries) { + $line = ([regex]::Match( + $definition, + '(?m)^.*' + [regex]::Escape($entry) + '.*$' + )).Value + if (-not $line.Contains('{#PersistentPowerShellPath}')) { + throw "Persistent command does not use the browser-accessible PowerShell path: $entry" + } + if ($line.Contains('{#PowerShellPath}') -or $line.Contains('{sysnative}')) { + throw "Persistent command still references sysnative, which 64-bit launchers cannot access: $entry" + } +} $uninstallStepIndex = $definition.IndexOf( 'if CurUninstallStep <> usUninstall then', From b80b45c2869c210edb116eda7e167180e6cfdc03 Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:50:41 +0800 Subject: [PATCH 2/8] accept normalized community apply uri --- windows/scripts/theme-windows.ps1 | 2 +- windows/tests/community-theme-link.tests.ps1 | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/windows/scripts/theme-windows.ps1 b/windows/scripts/theme-windows.ps1 index 17434594..43bd4d44 100644 --- a/windows/scripts/theme-windows.ps1 +++ b/windows/scripts/theme-windows.ps1 @@ -22,7 +22,7 @@ function Resolve-DreamSkinCommunityApplyUri { param([Parameter(Mandatory = $true)][string]$Uri) $match = [regex]::Match( $Uri, - '\Adreamskin://apply\?version=(ver_[a-z0-9]{8,64})\z', + '\Adreamskin://apply/?\?version=(ver_[a-z0-9]{8,64})\z', [System.Text.RegularExpressions.RegexOptions]::CultureInvariant ) if (-not $match.Success) { diff --git a/windows/tests/community-theme-link.tests.ps1 b/windows/tests/community-theme-link.tests.ps1 index 064f8ac0..c8070b87 100644 --- a/windows/tests/community-theme-link.tests.ps1 +++ b/windows/tests/community-theme-link.tests.ps1 @@ -21,6 +21,11 @@ if ($versionId -cne 'ver_1234abcd' -or -not (Test-DreamSkinCommunityVersionId -Value $versionId)) { throw 'The canonical community theme link did not resolve its version id.' } +$normalizedUri = 'dreamskin://apply/?version=ver_1234abcd' +$normalizedVersionId = Resolve-DreamSkinCommunityApplyUri -Uri $normalizedUri +if ($normalizedVersionId -cne 'ver_1234abcd') { + throw 'The browser-normalized community theme link did not resolve its version id.' +} $endpoints = Get-DreamSkinCommunityThemeEndpoints -VersionId $versionId if ($endpoints.MetadataUri -cne 'https://api.dreamskin.cc/v1/themes/ver_1234abcd' -or $endpoints.DownloadUri -cne 'https://api.dreamskin.cc/v1/themes/ver_1234abcd/download') { @@ -32,6 +37,8 @@ foreach ($invalidUri in @( 'dreamskin://apply?url=https://example.com/theme.zip', 'dreamskin://apply?version=ver_short', 'dreamskin://apply?version=ver_1234abcd&extra=1', + 'dreamskin://apply/?version=ver_1234abcd&extra=1', + 'dreamskin://apply//?version=ver_1234abcd', 'dreamskin://apply/path?version=ver_1234abcd', 'dreamskin://apply?version=ver_1234abcd#fragment', 'dreamskin://user@apply?version=ver_1234abcd', From 7724e6da38df5898e344b8d60d60662f0d0f6c1b Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:23:34 +0800 Subject: [PATCH 3/8] align windows theme store with colors contract --- windows/scripts/theme-windows.ps1 | 46 ++++++++++++------------ windows/tests/run-tests.ps1 | 1 + windows/tests/theme-zip-import.tests.ps1 | 9 +++++ 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/windows/scripts/theme-windows.ps1 b/windows/scripts/theme-windows.ps1 index 43bd4d44..39c414eb 100644 --- a/windows/scripts/theme-windows.ps1 +++ b/windows/scripts/theme-windows.ps1 @@ -378,10 +378,28 @@ function Read-DreamSkinTheme { Directory = $directory ThemePath = $themePath ImagePath = $imagePath - Theme = $theme + Theme = Normalize-DreamSkinThemeContract -Theme $theme } } +function Normalize-DreamSkinThemeContract { + param([Parameter(Mandatory = $true)][object]$Theme) + if ($null -eq $Theme -or $Theme -is [string] -or $Theme -is [array]) { + throw 'Theme contract must be a JSON object.' + } + if (-not $Theme.PSObject.Properties['id'] -or -not $Theme.PSObject.Properties['id'].Value) { + $Theme | Add-Member -NotePropertyName id -NotePropertyValue 'custom' -Force + } + if (-not $Theme.PSObject.Properties['appearance'] -or -not $Theme.PSObject.Properties['appearance'].Value) { + $Theme | Add-Member -NotePropertyName appearance -NotePropertyValue 'auto' -Force + } + if (-not $Theme.PSObject.Properties['art'] -or -not $Theme.PSObject.Properties['art'].Value) { + $Theme | Add-Member -NotePropertyName art -NotePropertyValue ` + ([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force + } + return $Theme +} + function Write-DreamSkinTheme { param( [Parameter(Mandatory = $true)][string]$ThemeDirectory, @@ -390,6 +408,7 @@ function Write-DreamSkinTheme { Assert-DreamSkinNoReparseComponents -Path $ThemeDirectory New-Item -ItemType Directory -Force -Path $ThemeDirectory | Out-Null Assert-DreamSkinNoReparseComponents -Path $ThemeDirectory + $Theme = Normalize-DreamSkinThemeContract -Theme $Theme $json = $Theme | ConvertTo-Json -Depth 8 $themePath = Join-Path $ThemeDirectory 'theme.json' Assert-DreamSkinNoReparseComponents -Path $themePath @@ -538,7 +557,6 @@ function Set-DreamSkinActiveTheme { name = '自定义主题' appearance = 'auto' art = [pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' } - palette = [pscustomobject]@{} } } $imageName = New-DreamSkinThemeImageName -Extension $extension @@ -564,15 +582,7 @@ function Set-DreamSkinActiveTheme { Assert-DreamSkinImageFile -Path $target $Theme | Add-Member -NotePropertyName image -NotePropertyValue $imageName -Force if ($Name) { $Theme | Add-Member -NotePropertyName name -NotePropertyValue $Name -Force } - if (-not $Theme.id) { $Theme | Add-Member -NotePropertyName id -NotePropertyValue 'custom' -Force } - if (-not $Theme.appearance) { $Theme | Add-Member -NotePropertyName appearance -NotePropertyValue 'auto' -Force } - if (-not $Theme.art) { - $Theme | Add-Member -NotePropertyName art -NotePropertyValue ` - ([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force - } - if (-not $Theme.palette) { - $Theme | Add-Member -NotePropertyName palette -NotePropertyValue ([pscustomobject]@{}) -Force - } + $Theme = Normalize-DreamSkinThemeContract -Theme $Theme $activeCss = Join-Path $paths.Active 'theme.css' Assert-DreamSkinNoReparseComponents -Path $activeCss if ($temporaryCss) { @@ -715,19 +725,7 @@ function Get-DreamSkinThemeRuntimeContentFingerprint { $loaded = Read-DreamSkinTheme -ThemeDirectory $ThemeDirectory -SkipImageMetadata $runtimeTheme = $loaded.Theme | ConvertTo-Json -Depth 8 | ConvertFrom-Json $runtimeTheme.image = '' - if (-not $runtimeTheme.id) { - $runtimeTheme | Add-Member -NotePropertyName id -NotePropertyValue 'custom' -Force - } - if (-not $runtimeTheme.appearance) { - $runtimeTheme | Add-Member -NotePropertyName appearance -NotePropertyValue 'auto' -Force - } - if (-not $runtimeTheme.art) { - $runtimeTheme | Add-Member -NotePropertyName art -NotePropertyValue ` - ([pscustomobject]@{ focusX = $null; focusY = $null; safeArea = 'auto'; taskMode = 'auto' }) -Force - } - if (-not $runtimeTheme.palette) { - $runtimeTheme | Add-Member -NotePropertyName palette -NotePropertyValue ([pscustomobject]@{}) -Force - } + $runtimeTheme = Normalize-DreamSkinThemeContract -Theme $runtimeTheme $canonicalTheme = ConvertTo-DreamSkinCanonicalJsonValue -Value $runtimeTheme $themeBytes = [System.Text.Encoding]::UTF8.GetBytes( ($canonicalTheme | ConvertTo-Json -Depth 8 -Compress) diff --git a/windows/tests/run-tests.ps1 b/windows/tests/run-tests.ps1 index 095b648d..b4405cf7 100644 --- a/windows/tests/run-tests.ps1 +++ b/windows/tests/run-tests.ps1 @@ -1018,6 +1018,7 @@ try { $updatedTheme.Theme.id -cne 'custom' -or $updatedTheme.Theme.art.safeArea -cne 'auto' -or $updatedTheme.Theme.art.taskMode -cne 'auto' -or + $updatedTheme.Theme.PSObject.Properties['palette'] -or -not (Test-DreamSkinThemePathWithin -Path $updatedTheme.ImagePath -Root $themePaths.Active)) { throw 'Imported image did not reset to the generic adaptive contract inside the managed directory.' } diff --git a/windows/tests/theme-zip-import.tests.ps1 b/windows/tests/theme-zip-import.tests.ps1 index ddb7385f..34654a7b 100644 --- a/windows/tests/theme-zip-import.tests.ps1 +++ b/windows/tests/theme-zip-import.tests.ps1 @@ -250,10 +250,19 @@ try { $roundtripPaths = Initialize-DreamSkinThemeStore -SkillRoot $Root ` -StateRoot $roundtripStateRoot $officialSaved = Read-DreamSkinTheme -ThemeDirectory $official.Path + if (-not $officialSaved.Theme.PSObject.Properties['colors'] -or + $officialSaved.Theme.PSObject.Properties['palette']) { + throw 'Studio colors-only theme was not preserved as the current community theme contract.' + } $officialTheme = $officialSaved.Theme | ConvertTo-Json -Depth 8 | ConvertFrom-Json $null = Set-DreamSkinActiveTheme -ImagePath $officialSaved.ImagePath ` -Theme $officialTheme -SafeCssPath (Join-Path $official.Path 'theme.css') ` -StateRoot $roundtripStateRoot + $roundtripActive = Read-DreamSkinTheme -ThemeDirectory $roundtripPaths.Active + if (-not $roundtripActive.Theme.PSObject.Properties['colors'] -or + $roundtripActive.Theme.PSObject.Properties['palette']) { + throw 'Applying a Studio colors-only theme added a legacy palette field.' + } $roundtripFingerprint = Get-DreamSkinThemeRuntimeContentFingerprint ` -ThemeDirectory $roundtripPaths.Active if ($roundtripFingerprint -cne $official.ContentFingerprint) { From 964458d7041163bb3d8e7744014de7755d9f9164 Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:28:46 +0800 Subject: [PATCH 4/8] align windows community theme contract --- windows/scripts/injector.mjs | 21 +++++++-------------- windows/tests/run-tests.ps1 | 6 ++++++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/windows/scripts/injector.mjs b/windows/scripts/injector.mjs index b31121dd..fab7a630 100644 --- a/windows/scripts/injector.mjs +++ b/windows/scripts/injector.mjs @@ -535,24 +535,17 @@ export async function loadTheme(themeDir) { throw new Error("Theme image cannot escape through a link or junction"); } const art = raw.art && typeof raw.art === "object" && !Array.isArray(raw.art) ? raw.art : {}; - const palette = raw.palette && typeof raw.palette === "object" && !Array.isArray(raw.palette) - ? raw.palette : {}; const rawColors = raw.colors && typeof raw.colors === "object" && !Array.isArray(raw.colors) ? raw.colors : null; const colorKeys = [ "background", "panel", "panelAlt", "accent", "accentAlt", "secondary", "highlight", "text", "muted", "line", ]; - const paletteAccent = typeof palette.accent === "string" && palette.accent.trim() - ? palette.accent.trim() : ""; - if (paletteAccent && !/^(?:#[\da-f]{3,8}|(?:rgb|hsl|oklch|oklab)\([^;{}]{1,96}\))$/i.test(paletteAccent)) { - throw new Error("palette.accent is not a supported CSS color"); - } const colors = { background: normalizeThemeColor(rawColors?.background, "#071116"), panel: normalizeThemeColor(rawColors?.panel, "#0b1a20"), panelAlt: normalizeThemeColor(rawColors?.panelAlt, "#10272c"), - accent: normalizeThemeColor(rawColors?.accent, normalizeThemeColor(paletteAccent, "#7cff46")), + accent: normalizeThemeColor(rawColors?.accent, "#7cff46"), accentAlt: normalizeThemeColor(rawColors?.accentAlt, "#b8ff3d"), secondary: normalizeThemeColor(rawColors?.secondary, "#36d7e8"), highlight: normalizeThemeColor(rawColors?.highlight, "#642a8c"), @@ -577,14 +570,10 @@ export async function loadTheme(themeDir) { safeArea: normalizedChoice(art.safeArea, "art.safeArea", THEME_CHOICES.safeArea, "auto"), taskMode: normalizedChoice(art.taskMode, "art.taskMode", THEME_CHOICES.taskMode, "auto"), }, - colorMode: rawColors ? "explicit" : (paletteAccent ? "explicit" : "auto"), - explicitColorKeys: rawColors - ? colorKeys.filter((key) => Object.hasOwn(rawColors, key)) - : (paletteAccent ? ["accent"] : []), + colorMode: rawColors ? "explicit" : "auto", + explicitColorKeys: rawColors ? colorKeys.filter((key) => Object.hasOwn(rawColors, key)) : [], colors, - palette: {}, }; - if (paletteAccent) theme.palette.accent = paletteAccent; const [themeStat, imageStat, safeCss] = await Promise.all([ fs.stat(themePath), fs.stat(realImagePath), @@ -1740,6 +1729,10 @@ if (path.resolve(process.argv[1] || "") === path.resolve(scriptPath)) { payloadBytes: Buffer.byteLength(loaded.payload), themeId: loaded.theme.id, appearance: loaded.theme.appearance, + colorMode: loaded.theme.colorMode, + explicitColorKeys: loaded.theme.explicitColorKeys, + hasColors: !!loaded.theme.colors && typeof loaded.theme.colors === "object", + hasPalette: Object.hasOwn(loaded.theme, "palette"), art: loaded.theme.art, artMetadata: loaded.theme.artMetadata ?? null, safeCssStatus: loaded.safeCssStatus, diff --git a/windows/tests/run-tests.ps1 b/windows/tests/run-tests.ps1 index b4405cf7..100d4fb2 100644 --- a/windows/tests/run-tests.ps1 +++ b/windows/tests/run-tests.ps1 @@ -1413,6 +1413,12 @@ try { $managedPayloadTest = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @( (Join-Path $Root 'scripts\injector.mjs'), '--check-payload', '--theme-dir', $themePaths.Active) if ($managedPayloadTest.ExitCode -ne 0) { throw 'Managed theme payload validation failed.' } + $managedPayload = ($managedPayloadTest.Output -join "`n") | ConvertFrom-Json + if (-not $managedPayload.pass -or $managedPayload.hasPalette -or -not $managedPayload.hasColors -or + $managedPayload.colorMode -notin @('auto', 'explicit') -or + $managedPayload.explicitColorKeys -isnot [array]) { + throw 'Windows payload drifted from the shared community theme contract.' + } $oversizedPayloadTest = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @( (Join-Path $Root 'scripts\injector.mjs'), '--check-payload', '--theme-dir', $oversizedTheme) if ($oversizedPayloadTest.ExitCode -eq 0) { throw 'Node injector accepted an image over the 10 MB limit.' } From 74927d445622cf2d771fcf358eee0071ec0ef6df Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:58:18 +0800 Subject: [PATCH 5/8] bound windows community apply verify --- windows/scripts/apply-community-theme.ps1 | 7 ++++++- windows/scripts/verify-dream-skin.ps1 | 1 + windows/tests/community-theme-link.tests.ps1 | 2 ++ windows/tests/run-tests.ps1 | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/windows/scripts/apply-community-theme.ps1 b/windows/scripts/apply-community-theme.ps1 index 426f2702..55e5bf58 100644 --- a/windows/scripts/apply-community-theme.ps1 +++ b/windows/scripts/apply-community-theme.ps1 @@ -308,7 +308,12 @@ function Invoke-DreamSkinCommunityStartAndVerify { ' -RequireUnpaused -OperationLockTimeoutMilliseconds ' + "$OperationLockTimeoutMilliseconds" $startProcess = Start-Process -FilePath $powershell -ArgumentList $argumentLine ` - -WindowStyle Hidden -Wait -PassThru + -WindowStyle Hidden -PassThru + if (-not $startProcess.WaitForExit($OperationLockTimeoutMilliseconds)) { + try { Stop-Process -InputObject $startProcess -Force -ErrorAction SilentlyContinue } catch {} + [void]$startProcess.WaitForExit(15000) + throw "Dream Skin start verification did not finish within $OperationLockTimeoutMilliseconds ms." + } if ($startProcess.ExitCode -ne 0) { throw "Dream Skin could not start and visibly verify the active theme (exit code $($startProcess.ExitCode))." } diff --git a/windows/scripts/verify-dream-skin.ps1 b/windows/scripts/verify-dream-skin.ps1 index 9276fb1b..935fc45c 100644 --- a/windows/scripts/verify-dream-skin.ps1 +++ b/windows/scripts/verify-dream-skin.ps1 @@ -8,6 +8,7 @@ $ErrorActionPreference = 'Stop' $PortExplicit = $PSBoundParameters.ContainsKey('Port') $injector = Join-Path $PSScriptRoot 'injector.mjs' . (Join-Path $PSScriptRoot 'common-windows.ps1') +. (Join-Path $PSScriptRoot 'theme-windows.ps1') $operationLock = Enter-DreamSkinOperationLock $verifyExitCode = 1 diff --git a/windows/tests/community-theme-link.tests.ps1 b/windows/tests/community-theme-link.tests.ps1 index c8070b87..ec23ef16 100644 --- a/windows/tests/community-theme-link.tests.ps1 +++ b/windows/tests/community-theme-link.tests.ps1 @@ -242,6 +242,8 @@ foreach ($requiredSafety in @( 'Set-DreamSkinActiveThemeFromSnapshot', 'Get-DreamSkinThemeRuntimeContentFingerprint', 'Invoke-DreamSkinCommunityStartAndVerify', + 'WaitForExit($OperationLockTimeoutMilliseconds)', + 'Dream Skin start verification did not finish within', 'Move-DreamSkinCommunityRollbackSnapshot', "['DreamSkinRecovery']", "Join-Path `$PSScriptRoot 'start-dream-skin.ps1'", diff --git a/windows/tests/run-tests.ps1 b/windows/tests/run-tests.ps1 index 100d4fb2..0afa55ba 100644 --- a/windows/tests/run-tests.ps1 +++ b/windows/tests/run-tests.ps1 @@ -1309,6 +1309,9 @@ try { throw 'Start lost the any-registered endpoint fallback for Store auto-updates.' } $verifyScriptSource = Read-DreamSkinUtf8File -Path (Join-Path $Root 'scripts\verify-dream-skin.ps1') + if (-not $verifyScriptSource.Contains(". (Join-Path `$PSScriptRoot 'theme-windows.ps1')")) { + throw 'Verify must dot-source theme-windows.ps1 before using theme store helpers such as Get-DreamSkinThemePaths.' + } if (-not $verifyScriptSource.Contains('Get-DreamSkinVerifiedCdpIdentityForAnyRegistered')) { throw 'Verify lost the any-registered endpoint fallback for Store auto-updates.' } From 71c3ca9bb9aeb4ed5d66fb3a58ee89e2b16598f7 Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:21:13 +0800 Subject: [PATCH 6/8] align windows visible apply verification --- windows/scripts/common-windows.ps1 | 13 ++++ windows/scripts/injector.mjs | 36 +++++++-- windows/scripts/start-dream-skin.ps1 | 13 ++++ .../tests/injector-window-readiness.test.mjs | 77 ++++++++++++++++++- windows/tests/run-tests.ps1 | 8 ++ .../tests/start-renderer-readiness.tests.ps1 | 9 ++- .../start-verified-skin-preserved.tests.ps1 | 4 + 7 files changed, 152 insertions(+), 8 deletions(-) diff --git a/windows/scripts/common-windows.ps1 b/windows/scripts/common-windows.ps1 index ca61c8cf..d59b2e87 100644 --- a/windows/scripts/common-windows.ps1 +++ b/windows/scripts/common-windows.ps1 @@ -1165,3 +1165,16 @@ function Confirm-DreamSkinRestart { $shell = New-Object -ComObject WScript.Shell return $shell.Popup($Message, 0, 'Codex Dream Skin', 52) -eq 6 } + +function Invoke-DreamSkinCodexWindowActivation { + param([Parameter(Mandatory = $true)][object]$Codex) + $processes = @(Get-DreamSkinCodexProcesses -Codex $Codex) + if ($processes.Count -eq 0) { return $false } + $shell = New-Object -ComObject WScript.Shell + foreach ($process in $processes) { + try { + if ($shell.AppActivate([int]$process.ProcessId)) { return $true } + } catch {} + } + return $false +} diff --git a/windows/scripts/injector.mjs b/windows/scripts/injector.mjs index fab7a630..d5269598 100644 --- a/windows/scripts/injector.mjs +++ b/windows/scripts/injector.mjs @@ -1142,10 +1142,27 @@ export async function verifySession( // already-resolved semantic home container so a healthy home session is // not rejected solely because the stricter home-icon selector is late. const home = document.querySelector(${selectorLiteral("home-route")}) ?? homeRoute; + const suggestions = home?.querySelector(${selectorLiteral("home-suggestions")}) ?? null; + const cardButtons = suggestions ? [...suggestions.querySelectorAll('button')] : []; + const cards = cardButtons.map(box); + const visibleCards = cards.filter((item) => item?.visible); + const suggestionLabels = cardButtons.flatMap((button) => { + const expectedColor = getComputedStyle(button).color; + return [...button.querySelectorAll('*')] + .filter((node) => [...node.childNodes].some((child) => + child.nodeType === 3 && child.textContent.trim())) + .map((node) => ({ + ...box(node), + text: String(node.textContent ?? "").trim().slice(0, 80), + color: getComputedStyle(node).color, + expectedColor, + })); + }); + const visibleSuggestionLabels = suggestionLabels.filter((item) => item?.visible); + const suggestionLabelColorsMatch = visibleSuggestionLabels.every((item) => + item.color === item.expectedColor); const settingsAnchor = document.querySelector(${selectorLiteral("appearance-radio")}) || document.querySelector(${stableTestidLiteral("theme-preview")}); - const suggestions = home?.querySelector(${selectorLiteral("home-suggestions")}) ?? null; - const cards = suggestions ? [...suggestions.querySelectorAll('button')].map(box) : []; const runtime = window.__CODEX_DREAM_SKIN_STATE__; const adopted = runtime?.styleMode === 'adopted' && [...document.adoptedStyleSheets].includes(runtime.styleSheet); @@ -1187,6 +1204,9 @@ export async function verifySession( settingsAnchor: box(settingsAnchor), hero, cards, + visibleCardCount: visibleCards.length, + suggestionLabels, + suggestionLabelColorsMatch, composer: box(document.querySelector(${selectorLiteral("composer-chrome")})), shell: box(document.querySelector(${selectorLiteral("shell-main")})), sidebar: box(document.querySelector(${selectorLiteral("left-panel")})), @@ -1227,12 +1247,18 @@ export async function verifySession( windowPass, documentPass, viewportPass, structurePass, nativeWindowPass, fallbackWindowPass, }; + const homePass = !result.homePresent || ( + Boolean(result.homeSurface?.visible && result.hero?.visible) && + result.hero.width >= 280 && result.hero.height >= 120 && + (!result.suggestionsPresent || result.visibleCardCount === 0 || ( + result.suggestionLabels.filter((item) => item?.visible).length >= result.visibleCardCount && + result.suggestionLabelColorsMatch + )) + ); result.pass = result.installed && result.version === result.expectedVersion && result.stylePresent && result.businessClassPollution === 0 && windowPass && documentPass && viewportPass && structurePass && - payloadPass && - (!result.homePresent || (Boolean(result.homeSurface?.visible && result.hero?.visible) && - (!result.suggestionsPresent || (result.cards.length >= 2 && result.cards.length <= 4)))); + payloadPass && homePass; return result; })()`); } diff --git a/windows/scripts/start-dream-skin.ps1 b/windows/scripts/start-dream-skin.ps1 index 6658ca82..93c4de72 100644 --- a/windows/scripts/start-dream-skin.ps1 +++ b/windows/scripts/start-dream-skin.ps1 @@ -281,6 +281,7 @@ try { # and a single early miss used to tear the whole startup down. The # watcher keeps applying in the background, so retry until a deadline. $verifyDeadline = (Get-Date).AddSeconds(90) + $forceInjectedAfterVerifyFailure = $false while ($true) { $verify = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @( $Injector, '--verify', '--port', "$Port", @@ -304,6 +305,18 @@ try { } catch { $skinLooksRendered = $false } + if (-not $forceInjectedAfterVerifyFailure) { + $forceInjectedAfterVerifyFailure = $true + try { [void](Invoke-DreamSkinCodexWindowActivation -Codex $codex) } catch {} + $once = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @( + $Injector, '--once', '--port', "$Port", + '--browser-id', $cdpIdentity.BrowserId, '--theme-dir', $themePaths.Active, + '--timeout-ms', '15000') + Write-DreamSkinUtf8FileAtomically -Path $VerifyPath -Content ( + (($verify.Output + $once.Output) -join "`r`n") + "`r`n" + ) + if ($once.ExitCode -eq 0) { break } + } if ($daemon.HasExited) { throw "The injector exited during startup. See $StderrPath" } if ((Get-Date) -ge $verifyDeadline) { throw "Dream Skin verification failed. See $VerifyPath" } Start-Sleep -Seconds 3 diff --git a/windows/tests/injector-window-readiness.test.mjs b/windows/tests/injector-window-readiness.test.mjs index a537969b..c19e2961 100644 --- a/windows/tests/injector-window-readiness.test.mjs +++ b/windows/tests/injector-window-readiness.test.mjs @@ -25,19 +25,49 @@ function makeRect(width = 800, height = 600, x = 0, y = 0) { return { x, y, width, height, right: x + width, bottom: y + height }; } -function makeElement({ rect = makeRect(), style = {}, visible = true } = {}) { - return { +function makeElement({ + rect = makeRect(), + style = {}, + visible = true, + text = "", + children = [], +} = {}) { + const element = { isConnected: true, + textContent: text, _style: { display: "block", visibility: "visible", contentVisibility: "visible", opacity: "1", + color: "rgb(240, 240, 240)", ...style, }, + childNodes: text ? [{ nodeType: 3, textContent: text }] : [], + children, getBoundingClientRect: () => rect, checkVisibility: () => visible, querySelector: () => null, + querySelectorAll: () => [], + }; + return element; +} + +function makeSuggestionButton({ + rect = makeRect(220, 80, 40, 300), + color = "rgb(210, 210, 210)", + labelColor = color, + text = "Suggestion", +} = {}) { + const label = makeElement({ + rect: makeRect(180, 24, rect.x + 12, rect.y + 12), + style: { color: labelColor }, + text, + }); + return { + ...makeElement({ rect, style: { color } }), + getBoundingClientRect: () => rect, + querySelectorAll: () => [label], }; } @@ -45,6 +75,8 @@ function makeHome(options = {}) { const home = makeElement(options); const hero = makeElement(options.hero ?? {}); home.firstElementChild = { firstElementChild: { firstElementChild: hero } }; + const suggestions = options.suggestions ?? null; + home.querySelector = (selector) => selector === selectors.suggestions ? suggestions : null; return home; } @@ -211,6 +243,47 @@ test("visible settings and home anchors are the only L0 structure exceptions", a assert.equal(noAnchor.result.readiness.structurePass, false); }); +test("home verification matches macOS and does not require a fixed suggestion-card count", async () => { + const oneSuggestion = { + querySelectorAll: (selector) => selector === "button" + ? [makeSuggestionButton({ text: "One real card" })] + : [], + }; + const homeWithOneSuggestion = await verify({ + dom: makeDomFixture({ + home: makeHome({ + rect: makeRect(900, 650, 20, 20), + hero: { rect: makeRect(800, 260, 40, 60) }, + suggestions: oneSuggestion, + }), + }), + }); + assert.equal(homeWithOneSuggestion.result.homePresent, true); + assert.equal(homeWithOneSuggestion.result.visibleCardCount, 1); + assert.equal(homeWithOneSuggestion.result.pass, true); + + const mismatchedSuggestion = { + querySelectorAll: (selector) => selector === "button" + ? [makeSuggestionButton({ + text: "Hidden by mismatched text color", + color: "rgb(210, 210, 210)", + labelColor: "rgb(10, 10, 10)", + })] + : [], + }; + const badHome = await verify({ + dom: makeDomFixture({ + home: makeHome({ + rect: makeRect(900, 650, 20, 20), + hero: { rect: makeRect(800, 260, 40, 60) }, + suggestions: mismatchedSuggestion, + }), + }), + }); + assert.equal(badHome.result.suggestionLabelColorsMatch, false); + assert.equal(badHome.result.pass, false); +}); + // Regression for #256. The previous version of this test asserted that a // -32000 "no window with given target found" reply must fail verification, and // went further than macOS by demanding the same for -32601. Codex 26.721.x diff --git a/windows/tests/run-tests.ps1 b/windows/tests/run-tests.ps1 index 0afa55ba..4df8230d 100644 --- a/windows/tests/run-tests.ps1 +++ b/windows/tests/run-tests.ps1 @@ -1294,6 +1294,14 @@ try { -not $startSource.Contains('Start-Sleep -Seconds 3')) { throw 'Start lost the verification retry window; a single early-boot miss must not tear the startup down.' } + if (-not $startSource.Contains('Invoke-DreamSkinCodexWindowActivation -Codex $codex') -or + -not $startSource.Contains("'--once'") -or + -not $startSource.Contains("'--timeout-ms', '15000'")) { + throw 'Start no longer mirrors macOS by activating Codex and force-injecting once after an initial visible-verification miss.' + } + if (-not (Get-Command Invoke-DreamSkinCodexWindowActivation -CommandType Function -ErrorAction SilentlyContinue)) { + throw 'The Windows Codex activation helper is missing from common-windows.ps1.' + } if (-not $startSource.Contains('direct Store executable fallback did not expose a verified loopback CDP endpoint') -or -not $startSource.Contains('may disable CDP in this production runtime')) { throw 'A direct launch that retains CDP arguments but exposes no listener no longer reports the owl runtime failure.' diff --git a/windows/tests/start-renderer-readiness.tests.ps1 b/windows/tests/start-renderer-readiness.tests.ps1 index 35ca0afa..c553092a 100644 --- a/windows/tests/start-renderer-readiness.tests.ps1 +++ b/windows/tests/start-renderer-readiness.tests.ps1 @@ -28,6 +28,7 @@ $script:daemon | Add-Member -MemberType ScriptMethod -Name WaitForExit -Value { } $script:dateCall = 0 $script:verifyCalls = 0 +$script:onceCalls = 0 $script:removeCalls = 0 $script:stateWritten = $false $script:stateRemoved = $false @@ -81,6 +82,7 @@ function Get-DreamSkinVerifiedCdpIdentity { } function Stop-DreamSkinRecordedInjector { param([object]$State); return $true } function Set-DreamSkinPaused { param([bool]$Paused, [string]$StateRoot); return $true } +function Invoke-DreamSkinCodexWindowActivation { param([object]$Codex); return $true } function ConvertTo-DreamSkinProcessArgument { param([string]$Value); return $Value } function Start-Process { [CmdletBinding()] @@ -105,6 +107,10 @@ function Invoke-DreamSkinNative { $script:verifyCalls += 1 return [pscustomobject]@{ ExitCode = 2; Output = @('{"pass":false}') } } + if ($ArgumentList -contains '--once') { + $script:onceCalls += 1 + return [pscustomobject]@{ ExitCode = 2; Output = @('{"mode":"once","targets":[]}') } + } if ($ArgumentList -contains '--remove') { $script:removeCalls += 1 return [pscustomobject]@{ ExitCode = 0; Output = @() } @@ -152,7 +158,8 @@ try { $announcedActive = @($script:hostMessages | Where-Object { $_ -like 'Codex Dream Skin is active*' }).Count -gt 0 -if (-not $failed -or $script:verifyCalls -ne 1 -or $script:removeCalls -ne 1 -or +if (-not $failed -or $script:verifyCalls -ne 1 -or $script:onceCalls -ne 1 -or + $script:removeCalls -ne 1 -or -not $script:stateWritten -or -not $script:stateRemoved -or -not $script:daemonStopped -or -not $script:daemon.HasExited -or -not $script:lockExited -or $announcedActive) { diff --git a/windows/tests/start-verified-skin-preserved.tests.ps1 b/windows/tests/start-verified-skin-preserved.tests.ps1 index c3ce10a7..412bab6d 100644 --- a/windows/tests/start-verified-skin-preserved.tests.ps1 +++ b/windows/tests/start-verified-skin-preserved.tests.ps1 @@ -79,6 +79,7 @@ function Invoke-DreamSkinStartupFixture { function Test-DreamSkinPathEqual { param([string]$Left, [string]$Right); return $true } function Stop-DreamSkinRecordedInjector { param([object]$State); return $true } function Set-DreamSkinPaused { param([bool]$Paused, [string]$StateRoot); return $true } + function Invoke-DreamSkinCodexWindowActivation { param([object]$Codex); return $true } function ConvertTo-DreamSkinProcessArgument { param([string]$Value); return $Value } function Get-DreamSkinProcessStartedAt { param([int]$ProcessId); return '2026-07-25T00:00:00.0000000Z' } function Write-DreamSkinState { param([string]$Path, [object]$State) } @@ -106,6 +107,9 @@ function Invoke-DreamSkinStartupFixture { if ($ArgumentList -contains '--verify') { return [pscustomobject]@{ ExitCode = 2; Output = @($script:verifyPayload) } } + if ($ArgumentList -contains '--once') { + return [pscustomobject]@{ ExitCode = 2; Output = @($script:verifyPayload) } + } if ($ArgumentList -contains '--remove') { return [pscustomobject]@{ ExitCode = 0; Output = @() } } From 1ea902b9c50d470e3b053fb4aef19459b5715f25 Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:22:57 +0800 Subject: [PATCH 7/8] fix(windows): harden community theme apply --- TASK_PROGRESS.md | 14 +++++++ windows/scripts/apply-community-theme.ps1 | 11 ++++- windows/scripts/common-windows.ps1 | 20 ++++++--- windows/tests/community-theme-link.tests.ps1 | 17 +++++++- windows/tests/run-tests.ps1 | 44 ++++++++++++++++++++ 5 files changed, 99 insertions(+), 7 deletions(-) diff --git a/TASK_PROGRESS.md b/TASK_PROGRESS.md index 9ca556ac..6fb7e0dc 100644 --- a/TASK_PROGRESS.md +++ b/TASK_PROGRESS.md @@ -1,5 +1,19 @@ # Task Progress +Updated: 2026-07-30 18:00 CST (Asia/Shanghai) + +## PR #317 Real Windows Community Apply Follow-up + +- [verified] `pr-317` is at remote PR head `71c3ca9`; all four PR CI jobs pass. +- [verified] Real `dreamskin://apply/?version=ver_6fac938806981a73cb51` handoff reaches the native confirmation, fixed production API, strict ZIP import, and rollback. The imported `axdlee / Quiet Paper` payload also passes exact live renderer verification when applied directly (`themeId`, revision, visible home/shell/sidebar/hero/cards, text colors, and overflow all pass). +- [root cause] The transaction failed before target verification because Windows reported `The recorded Dream Skin injector did not stop` after its five-second PID-based wait. The generic transaction error incorrectly surfaced this as a visible-verification failure. The immediately following rollback start stopped the same watcher and visibly verified the prior theme. +- [verified] The identity-validated `Process` object wait fix passes the full Windows suite, a new real watcher-process shutdown fixture, installer static checks, Setup.exe build/install, and installed standalone Verify. The rebuilt package SHA-256 is `FF7724751A3C9EB4046590CF869AE15CE41F3441D5620B28049EB6C952B375ED`. +- [root cause] The next real transaction applied and visibly verified `Quiet Paper`, exited the handler, and cleaned its work directory, but the success dialog failed: Windows PowerShell 5.1 treats curly double quotes as string delimiters, so `"“$name”..."` bound an empty value to `Message`. The active target remained correctly applied. +- [complete] The Windows success text now uses PowerShell-safe `「」` brackets and has an executable PowerShell 5.1 regression. Rebuilt Setup.exe SHA-256: `2489092FFBA60ABC119A1612A3076773DBEF5910EFD8BC4ECB64840A6C710B76`. +- [verified] Final real registered-protocol apply of `ver_6fac938806981a73cb51` completed in 38.9 seconds: native confirmation, production metadata/download, strict duplicate import, active-theme transaction, exact visible renderer verification, native success message, zero remaining handler processes, and zero `.community-apply-*` directories. The target screenshot SHA-256 is `42472EBE7E7B0A73F600C159E074D547B163604AA1857A5E70F91898853A7C5E`. +- [verified] The complete Windows suite and installer static suite pass after both fixes. The pre-test `preset-gothic-void-crusade` theme was restored and standalone Verify passes. +- [gap] The in-app Browser plugin reported no available browser instances, so this final run started from the installed `dreamskin://` registration rather than clicking the DreamSkin.cc web button. Earlier site-to-protocol behavior was not treated as newly reverified here. + Updated: 2026-07-25 08:31 HKT (Asia/Hong_Kong) ## v1.5.1 Version Release (2026-07-25 08:28 HKT) diff --git a/windows/scripts/apply-community-theme.ps1 b/windows/scripts/apply-community-theme.ps1 index 55e5bf58..4104e15a 100644 --- a/windows/scripts/apply-community-theme.ps1 +++ b/windows/scripts/apply-community-theme.ps1 @@ -29,6 +29,15 @@ function Show-DreamSkinCommunityMessage { ) } +function Format-DreamSkinCommunitySuccessMessage { + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Name + ) + return "主题「$Name」已通过下载、SHA-256、主题包与 Safe CSS 校验,并已应用到 Codex。" +} + function Confirm-DreamSkinCommunityApply { param([Parameter(Mandatory = $true)][object]$Metadata) Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop @@ -768,7 +777,7 @@ try { $result = Invoke-DreamSkinCommunityApply -ApplyUri $Uri if (-not $result.Canceled) { Show-DreamSkinCommunityMessage ` - -Message "“$($result.Name)”已通过下载、SHA-256、主题包与 Safe CSS 校验,并已应用到 Codex。" + -Message (Format-DreamSkinCommunitySuccessMessage -Name $result.Name) } exit 0 } catch { diff --git a/windows/scripts/common-windows.ps1 b/windows/scripts/common-windows.ps1 index d59b2e87..e33dd691 100644 --- a/windows/scripts/common-windows.ps1 +++ b/windows/scripts/common-windows.ps1 @@ -1052,8 +1052,13 @@ function Stop-DreamSkinRecordedInjector { param([AllowNull()][object]$State) if ($null -eq $State -or -not $State.injectorPid) { return $true } $processId = [int]$State.injectorPid + $processHandle = Get-Process -Id $processId -ErrorAction SilentlyContinue + if (-not $processHandle) { return $true } $process = Get-CimInstance Win32_Process -Filter "ProcessId = $processId" -ErrorAction SilentlyContinue - if (-not $process) { return $true } + if (-not $process) { + if ($processHandle.HasExited) { return $true } + throw "The recorded injector PID $processId is running, but its identity cannot be inspected. State was preserved." + } $expectedInjector = if ($State.injectorPath) { "$($State.injectorPath)" @@ -1083,7 +1088,12 @@ function Stop-DreamSkinRecordedInjector { $browserPattern = '(?:^|\s)(?i:--browser-id)(?:=|\s+)' + [regex]::Escape("$($State.browserId)") + '(?=$|\s)' $injectorMatches = $injectorMatches -and [regex]::IsMatch($commandLine, $browserPattern) } - $startedAt = Get-DreamSkinProcessStartedAt -ProcessId $processId + try { + $startedAt = $processHandle.StartTime.ToUniversalTime().ToString('o') + } catch { + if ($processHandle.HasExited) { return $true } + throw "The recorded injector PID $processId is running, but its start time cannot be inspected. State was preserved." + } $startMatches = -not $State.injectorStartedAt -or $startedAt -eq "$($State.injectorStartedAt)" $identityMatches = [bool]($isNodeExecutable -and $nodeMatches -and $injectorMatches -and $startMatches) @@ -1091,9 +1101,9 @@ function Stop-DreamSkinRecordedInjector { throw "The recorded injector PID $processId is running, but its visible identity does not match the saved Dream Skin process. State was preserved." } - Stop-Process -Id $processId -Force -ErrorAction Stop - try { Wait-Process -Id $processId -Timeout 5 -ErrorAction Stop } catch {} - if (Get-Process -Id $processId -ErrorAction SilentlyContinue) { + Stop-Process -InputObject $processHandle -Force -ErrorAction Stop + [void]$processHandle.WaitForExit(15000) + if (-not $processHandle.HasExited) { throw "The recorded Dream Skin injector did not stop: PID $processId" } return $true diff --git a/windows/tests/community-theme-link.tests.ps1 b/windows/tests/community-theme-link.tests.ps1 index ec23ef16..cf494f7c 100644 --- a/windows/tests/community-theme-link.tests.ps1 +++ b/windows/tests/community-theme-link.tests.ps1 @@ -204,8 +204,22 @@ $requestHelperAst = $applyAst.Find({ $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -ceq 'New-DreamSkinCommunityHttpRequest' }, $true) -if ($null -eq $requestHelperAst) { throw 'The fixed-origin community request helper is missing.' } +$successMessageHelperAst = $applyAst.Find({ + param($node) + $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and + $node.Name -ceq 'Format-DreamSkinCommunitySuccessMessage' +}, $true) +if ($null -eq $requestHelperAst -or $null -eq $successMessageHelperAst) { + throw 'The fixed-origin request or success-message helper is missing.' +} Invoke-Expression $requestHelperAst.Extent.Text +Invoke-Expression $successMessageHelperAst.Extent.Text +$successMessage = Format-DreamSkinCommunitySuccessMessage -Name 'Paper' +if (-not $successMessage -or $successMessage -notmatch 'Paper' -or + $successMessage -notmatch 'SHA-256' -or $successMessage -notmatch 'Safe CSS' -or + $successMessage -notmatch 'Codex') { + throw 'The Windows PowerShell 5.1 community success message is empty or malformed.' +} $request = New-DreamSkinCommunityHttpRequest ` -RequestUri 'https://api.dreamskin.cc/v1/themes/ver_1234abcd' -Accept 'application/json' if ($request.AllowAutoRedirect -or @@ -249,6 +263,7 @@ foreach ($requiredSafety in @( "Join-Path `$PSScriptRoot 'start-dream-skin.ps1'", "' -RestartExisting'", 'Restore-DreamSkinActiveThemeSnapshot', + 'Format-DreamSkinCommunitySuccessMessage -Name $result.Name', 'Remove-Item -LiteralPath $workRoot -Recurse -Force' )) { if (-not $applySource.Contains($requiredSafety)) { diff --git a/windows/tests/run-tests.ps1 b/windows/tests/run-tests.ps1 index 4df8230d..024fabc8 100644 --- a/windows/tests/run-tests.ps1 +++ b/windows/tests/run-tests.ps1 @@ -1302,6 +1302,11 @@ try { if (-not (Get-Command Invoke-DreamSkinCodexWindowActivation -CommandType Function -ErrorAction SilentlyContinue)) { throw 'The Windows Codex activation helper is missing from common-windows.ps1.' } + if (-not $commonSource.Contains('Stop-Process -InputObject $processHandle -Force') -or + -not $commonSource.Contains('[void]$processHandle.WaitForExit(15000)') -or + -not $commonSource.Contains('if (-not $processHandle.HasExited)')) { + throw 'Recorded injector shutdown must wait on the exact validated process object before startup continues.' + } if (-not $startSource.Contains('direct Store executable fallback did not expose a verified loopback CDP endpoint') -or -not $startSource.Contains('may disable CDP in this production runtime')) { throw 'A direct launch that retains CDP arguments but exposes no listener no longer reports the owl runtime failure.' @@ -1404,6 +1409,45 @@ try { throw 'Mismatched live injector identity does not fail closed with preserved state.' } + $recordedInjectorFixture = Join-Path $temporaryRoot 'recorded-injector-fixture.mjs' + [System.IO.File]::WriteAllText( + $recordedInjectorFixture, + "setInterval(() => {}, 600000);`n", + [System.Text.UTF8Encoding]::new($false) + ) + $recordedInjectorPort = 49333 + $recordedInjectorBrowserId = 'fixture-browser' + $recordedInjectorArguments = (ConvertTo-DreamSkinProcessArgument -Value $recordedInjectorFixture) + + " --watch --port $recordedInjectorPort --browser-id $recordedInjectorBrowserId" + $recordedInjectorProcess = Start-Process -FilePath $node.Path ` + -ArgumentList $recordedInjectorArguments -WindowStyle Hidden -PassThru + try { + Start-Sleep -Milliseconds 250 + if ($recordedInjectorProcess.HasExited) { + throw 'Recorded injector shutdown fixture exited before its identity could be tested.' + } + $recordedInjectorState = [pscustomobject]@{ + injectorPid = $recordedInjectorProcess.Id + injectorStartedAt = $recordedInjectorProcess.StartTime.ToUniversalTime().ToString('o') + injectorPath = $recordedInjectorFixture + nodePath = $node.Path + port = $recordedInjectorPort + browserId = $recordedInjectorBrowserId + } + if (-not (Stop-DreamSkinRecordedInjector -State $recordedInjectorState)) { + throw 'The identity-validated recorded injector did not report a successful stop.' + } + $recordedInjectorProcess.Refresh() + if (-not $recordedInjectorProcess.HasExited) { + throw 'The identity-validated recorded injector was still running after shutdown returned.' + } + } finally { + if (-not $recordedInjectorProcess.HasExited) { + Stop-Process -InputObject $recordedInjectorProcess -Force -ErrorAction SilentlyContinue + [void]$recordedInjectorProcess.WaitForExit(15000) + } + } + $stderrProbe = Invoke-DreamSkinNative -FilePath $node.Path -ArgumentList @( '-e', "process.stderr.write('dream-skin-stderr-probe\n'); process.exit(7)") if ($stderrProbe.ExitCode -ne 7 -or ($stderrProbe.Output -join "`n") -notmatch 'dream-skin-stderr-probe') { From 9e00c1c765e88f906bb795561d6f39c0c7593022 Mon Sep 17 00:00:00 2001 From: Fei-Away <100042407+Fei-Away@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:36:32 +0800 Subject: [PATCH 8/8] exclude task progress from pr --- TASK_PROGRESS.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/TASK_PROGRESS.md b/TASK_PROGRESS.md index 6fb7e0dc..9ca556ac 100644 --- a/TASK_PROGRESS.md +++ b/TASK_PROGRESS.md @@ -1,19 +1,5 @@ # Task Progress -Updated: 2026-07-30 18:00 CST (Asia/Shanghai) - -## PR #317 Real Windows Community Apply Follow-up - -- [verified] `pr-317` is at remote PR head `71c3ca9`; all four PR CI jobs pass. -- [verified] Real `dreamskin://apply/?version=ver_6fac938806981a73cb51` handoff reaches the native confirmation, fixed production API, strict ZIP import, and rollback. The imported `axdlee / Quiet Paper` payload also passes exact live renderer verification when applied directly (`themeId`, revision, visible home/shell/sidebar/hero/cards, text colors, and overflow all pass). -- [root cause] The transaction failed before target verification because Windows reported `The recorded Dream Skin injector did not stop` after its five-second PID-based wait. The generic transaction error incorrectly surfaced this as a visible-verification failure. The immediately following rollback start stopped the same watcher and visibly verified the prior theme. -- [verified] The identity-validated `Process` object wait fix passes the full Windows suite, a new real watcher-process shutdown fixture, installer static checks, Setup.exe build/install, and installed standalone Verify. The rebuilt package SHA-256 is `FF7724751A3C9EB4046590CF869AE15CE41F3441D5620B28049EB6C952B375ED`. -- [root cause] The next real transaction applied and visibly verified `Quiet Paper`, exited the handler, and cleaned its work directory, but the success dialog failed: Windows PowerShell 5.1 treats curly double quotes as string delimiters, so `"“$name”..."` bound an empty value to `Message`. The active target remained correctly applied. -- [complete] The Windows success text now uses PowerShell-safe `「」` brackets and has an executable PowerShell 5.1 regression. Rebuilt Setup.exe SHA-256: `2489092FFBA60ABC119A1612A3076773DBEF5910EFD8BC4ECB64840A6C710B76`. -- [verified] Final real registered-protocol apply of `ver_6fac938806981a73cb51` completed in 38.9 seconds: native confirmation, production metadata/download, strict duplicate import, active-theme transaction, exact visible renderer verification, native success message, zero remaining handler processes, and zero `.community-apply-*` directories. The target screenshot SHA-256 is `42472EBE7E7B0A73F600C159E074D547B163604AA1857A5E70F91898853A7C5E`. -- [verified] The complete Windows suite and installer static suite pass after both fixes. The pre-test `preset-gothic-void-crusade` theme was restored and standalone Verify passes. -- [gap] The in-app Browser plugin reported no available browser instances, so this final run started from the installed `dreamskin://` registration rather than clicking the DreamSkin.cc web button. Earlier site-to-protocol behavior was not treated as newly reverified here. - Updated: 2026-07-25 08:31 HKT (Asia/Hong_Kong) ## v1.5.1 Version Release (2026-07-25 08:28 HKT)