| 1 | # The selector is deliberately scoped to the rendered transcript. Sidebar topic |
| 2 | # titles and offscreen accessibility nodes are not evidence of restored history. |
| 3 | function Get-UpgradeUIDescendants($element) { |
| 4 | return $element.FindAll([Windows.Automation.TreeScope]::Descendants, [Windows.Automation.Condition]::TrueCondition) |
| 5 | } |
| 6 | |
| 7 | function Invoke-UpgradeUIButton($element) { |
| 8 | $pattern = $element.GetCurrentPattern([Windows.Automation.InvokePattern]::Pattern) |
| 9 | $pattern.Invoke() |
| 10 | } |
| 11 | |
| 12 | function Invoke-PendingHistoricalSession($root) { |
| 13 | foreach ($element in (Get-UpgradeUIDescendants $root)) { |
| 14 | $current = $element.Current |
| 15 | if ($current.AutomationId -eq 'reasonix-prepare-restored-session' -and -not $current.IsOffscreen -and |
| 16 | $current.BoundingRectangle.Width -gt 0 -and $current.BoundingRectangle.Height -gt 0) { |
| 17 | Invoke-UpgradeUIButton $element |
| 18 | return $true |
| 19 | } |
| 20 | } |
| 21 | return $false |
| 22 | } |
| 23 | |
| 24 | function Get-UpgradeUITimeMilliseconds { |
| 25 | return [long]([Diagnostics.Stopwatch]::GetTimestamp() * 1000.0 / [Diagnostics.Stopwatch]::Frequency) |
| 26 | } |
| 27 | |
| 28 | function Wait-UpgradeUIPoll { Start-Sleep -Milliseconds 250 } |
| 29 | |
| 30 | # This is functional recovery acceptance, not a cold-import latency SLO. A |
| 31 | # loaded Windows runner can spend the old entire 20-second budget importing, |
| 32 | # activating and exposing the first Markdown body through UI Automation. Keep |
| 33 | # a bounded completion wait and record its cost; startup readiness has its own |
| 34 | # separate deadline. Always sample before checking the deadline, including the |
| 35 | # final observation, instead of failing on a previously sampled empty surface. |
| 36 | function Wait-VisibleUpgradeHistory { |
| 37 | param( |
| 38 | [Parameter(Mandatory=$true)][scriptblock]$ReadRoot, |
| 39 | [Parameter(Mandatory=$true)][string]$Text, |
| 40 | [switch]$PrepareHistoricalSession, |
| 41 | [ValidateRange(1,300)][int]$TimeoutSeconds = 60 |
| 42 | ) |
| 43 | $started = Get-UpgradeUITimeMilliseconds |
| 44 | $prepared = -not $PrepareHistoricalSession |
| 45 | $found = $false |
| 46 | $root = $null |
| 47 | $polls = 0 |
| 48 | while ($true) { |
| 49 | $root = & $ReadRoot |
| 50 | $polls++ |
| 51 | if ($null -ne $root) { |
| 52 | if (-not $prepared) { $prepared = Invoke-PendingHistoricalSession $root } |
| 53 | # Lazy history is readable without activating a runtime. The import |
| 54 | # action blocks acceptance only when this wait was asked to prepare; |
| 55 | # otherwise it is the banner every unprepared legacy tab carries. |
| 56 | $found = Test-VisibleUpgradeHistory $root $Text -AllowPendingSource:(-not $PrepareHistoricalSession) |
| 57 | if ($found) { $prepared = $true } |
| 58 | } |
| 59 | $elapsed = (Get-UpgradeUITimeMilliseconds) - $started |
| 60 | if ($found -or $elapsed -ge ($TimeoutSeconds * 1000)) { break } |
| 61 | Wait-UpgradeUIPoll |
| 62 | } |
| 63 | return [pscustomobject]@{Root=$root; Found=$found; Prepared=$prepared; ElapsedMilliseconds=$elapsed; Polls=$polls; TimeoutSeconds=$TimeoutSeconds} |
| 64 | } |
| 65 | |
| 66 | function Test-VisibleUpgradeHistory($root, [string]$text, [switch]$AllowPendingSource) { |
| 67 | if ([string]::IsNullOrWhiteSpace($text)) { return $false } |
| 68 | $descendants = @(Get-UpgradeUIDescendants $root) |
| 69 | foreach ($element in $descendants) { |
| 70 | $current = $element.Current |
| 71 | if ($current.IsOffscreen -or $current.BoundingRectangle.Width -le 0 -or $current.BoundingRectangle.Height -le 0) { continue } |
| 72 | # Old content alone cannot prove recovery: reject a still-loading/failed |
| 73 | # recovery surface and the obsolete chat notice that originally hid this bug. |
| 74 | if (([string]$current.AutomationId).StartsWith('reasonix-session-recovery-') -or |
| 75 | (-not $AllowPendingSource -and $current.AutomationId -eq 'reasonix-prepare-restored-session') -or |
| 76 | ([string]$current.Name).Contains('Failed to load conversation history.') -or |
| 77 | ([string]$current.Name).Contains('加载会话历史失败。') -or |
| 78 | ([string]$current.Name).Contains('載入會話歷史失敗。')) { return $false } |
| 79 | } |
| 80 | foreach ($transcript in $descendants) { |
| 81 | if (-not ([string]$transcript.Current.AutomationId).StartsWith('reasonix-chat-transcript-') -or $transcript.Current.IsOffscreen) { continue } |
| 82 | foreach ($element in (Get-UpgradeUIDescendants $transcript)) { |
| 83 | $current = $element.Current |
| 84 | if (-not $current.IsOffscreen -and $current.BoundingRectangle.Width -gt 0 -and $current.BoundingRectangle.Height -gt 0 -and |
| 85 | $current.Name.Contains($text)) { return $true } |
| 86 | } |
| 87 | } |
| 88 | return $false |
| 89 | } |
| 90 |