diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e3b8bbe9..d371698eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
# Changelog
+## Unreleased
+
+Fixes for the three alpha3 field reports.
+
+### Fixed
+
+- Opening a PowerShell tab no longer raises an antivirus alert. The `touch`
+ compatibility command created files through a raw `File::Open` write call,
+ which — combined with the native-dispatch blocks in the same script — read
+ as a write-then-execute shape to antivirus heuristics and flagged the whole
+ module at load, on some engines with a user-visible warning for every new
+ tab. File creation now goes through `New-Item`; behavior is unchanged
+ (create if missing, update the timestamp without truncating otherwise), and
+ the module loads clean under the engine that previously flagged it.
+- Completed commands now show ✓/✕ instead of staying `? Unknown`. The Enter
+ keypress heuristic that supplies the command-executed transition only set
+ buffer marks and never notified the shell-integration lifecycle, so the
+ chain never observed the executed stage, the pane's capability never read
+ Full, and the presentation downgraded every trusted result to Unknown. The
+ Enter path now reports the executed transition when the mark was
+ established by the shell and the input line is non-empty; a heuristic-only
+ mark (no shell integration, for example cmd.exe) still reports nothing.
+ This also restores the Running status while a command executes.
+- A long Timeline command is now readable in full: every row carries a
+ tooltip with the complete command text, wrapped, with no marquee animation.
+- The `Unknown` status now explains itself: hovering it shows that the shell
+ did not report a result for this command, and that success and failure are
+ shown only when shell integration reports them.
+
## 1.3.0-alpha3 - 2026-08-04
Third alpha prerelease: fixes for the five alpha2 field reports. Like the
diff --git a/scripts/winterm/test-command-timeline.ps1 b/scripts/winterm/test-command-timeline.ps1
index 74f03e313..e1d8fb92a 100644
--- a/scripts/winterm/test-command-timeline.ps1
+++ b/scripts/winterm/test-command-timeline.ps1
@@ -270,6 +270,7 @@ Assert-Contains -Content $resources -Values @(
'CommandTimelineCopyOutput',
'CommandTimelineJumpToOutput',
'CommandTimelineOutputUnavailable',
+ 'CommandTimelineStatusUnknownDetail',
'CommandTimelineMultilineBlocked',
'CommandTimelineConfirmLoad'
) -Failure 'Timeline entry-action localized strings are missing.'
diff --git a/shell/powershell/winTerm.Shell/Public/Compatibility.ps1 b/shell/powershell/winTerm.Shell/Public/Compatibility.ps1
index acdbc6969..5237e2133 100644
--- a/shell/powershell/winTerm.Shell/Public/Compatibility.ps1
+++ b/shell/powershell/winTerm.Shell/Public/Compatibility.ps1
@@ -184,8 +184,12 @@ function touch
try
{
- $stream = [System.IO.File]::Open($fileSystemPath, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)
- $stream.Dispose()
+ # The existence test above makes creation the only reachable case,
+ # so New-Item never truncates an existing file here. The previous
+ # raw File::Open write call also read as a write-then-execute shape
+ # to antivirus heuristics when combined with the native-dispatch
+ # blocks in this file, and flagged the whole module at load.
+ New-Item -ItemType File -Path $fileSystemPath -ErrorAction Stop | Out-Null
}
catch
{
diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
index de4b1842e..2c3dcd80f 100644
--- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
@@ -376,6 +376,9 @@
Unknown
+
+ The shell did not report a result for this command. Success and failure are shown only when shell integration reports them.
+
Copy command
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 4bd425e75..3640fc8ef 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -3013,6 +3013,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Controls::TextBlock status;
status.Text(winrt::hstring{ std::wstring{ statusGlyph } + L" " + std::wstring{ statusLabel } });
status.FontSize(11);
+ if (entry.executionResult == winTerm::CommandTimeline::ExecutionResult::Unknown)
+ {
+ // Unknown is a trust statement, not an error; say so where the
+ // question arises.
+ Controls::ToolTipService::SetToolTip(status, box_value(RS_(L"CommandTimelineStatusUnknownDetail")));
+ }
Controls::StackPanel content;
content.Spacing(2);
@@ -3026,6 +3032,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
item.HorizontalContentAlignment(HorizontalAlignment::Stretch);
item.IsTabStop(false);
+ // The row shows one trimmed line; the tooltip carries the full
+ // command text so a long command stays readable without any
+ // marquee animation. The text is already on screen in this pane,
+ // so the tooltip introduces no new exposure.
+ Controls::TextBlock fullCommand;
+ fullCommand.Text(winrt::hstring{ commandText });
+ fullCommand.TextWrapping(TextWrapping::Wrap);
+ fullCommand.MaxWidth(480.0);
+ Controls::ToolTip rowTip;
+ rowTip.Content(fullCommand);
+ Controls::ToolTipService::SetToolTip(item, rowTip);
+
const auto accessibleName = commandText + L", " + std::wstring{ statusLabel };
Windows::UI::Xaml::Automation::AutomationProperties::SetName(item, winrt::hstring{ accessibleName });
Windows::UI::Xaml::Automation::AutomationProperties::SetPositionInSet(
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index 13aee09d2..72197b422 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -771,6 +771,18 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s
// This changed the scrollbar marks - raise a notification to update them
_NotifyScrollEvent();
+ }
+ else if (!_mainBuffer->CurrentCommandTimelineCommand().empty())
+ {
+ // The shell established this mark through OSC 133 A/B, and
+ // this Enter keypress supplies the command-executed
+ // transition the shell itself does not report. Without this
+ // notification the lifecycle chain never observes the
+ // executed stage, the capability never reads Full, and every
+ // completed command presents as Unknown. A heuristic-only
+ // mark (createdMark above) stays buffer-local and reports
+ // nothing, and an empty input line starts no command.
+ NotifyShellIntegrationMark(::Microsoft::Console::VirtualTerminal::ShellIntegrationMarkKind::CommandExecuted, std::nullopt);
}
}
}
diff --git a/src/cascadia/UnitTests_Control/CommandTimelineTests.cpp b/src/cascadia/UnitTests_Control/CommandTimelineTests.cpp
index 204759145..197dc6c47 100644
--- a/src/cascadia/UnitTests_Control/CommandTimelineTests.cpp
+++ b/src/cascadia/UnitTests_Control/CommandTimelineTests.cpp
@@ -23,6 +23,7 @@ namespace ControlUnitTests
TEST_METHOD(ColdBootstrapScansOnceAndWarmAccessDoesNotRescan);
TEST_METHOD(LifecycleUpdatesAreIncrementalAndIdempotent);
TEST_METHOD(OutOfOrderLifecycleStaysUnknown);
+ TEST_METHOD(EnterKeypressSuppliesExecutedTransition);
TEST_METHOD(TrustedCompletionMapsResults);
TEST_METHOD(CapabilityRequiresCompleteNativeLifecycle);
TEST_METHOD(InvalidationAndEvictionPruneGhostEntries);
@@ -227,6 +228,62 @@ namespace ControlUnitTests
VERIFY_IS_NULL(index.Current());
}
+ void CommandTimelineTests::EnterKeypressSuppliesExecutedTransition()
+ {
+ // The supported PowerShell flow emits 133;A/B/D from the shell and
+ // relies on the Enter keypress heuristic for the executed transition.
+ // This drives that exact flow through a live core: no 133;C ever
+ // arrives on the wire.
+ auto settings = winrt::make_self();
+ settings->AutoMarkPrompts(true);
+ auto connection = winrt::make_self();
+ auto core = winrt::make_self(*settings, *settings, *connection);
+ core->_inUnitTests = true;
+ auto cleanup = wil::scope_exit([&]() noexcept {
+ try
+ {
+ if (core)
+ {
+ core->Close();
+ }
+ }
+ catch (...)
+ {
+ LOG_CAUGHT_EXCEPTION();
+ }
+ core = nullptr;
+ connection = nullptr;
+ settings = nullptr;
+ });
+ VERIFY_IS_TRUE(core->Initialize(270, 380, 1.0));
+
+ connection->WriteInput(winrt_wstring_to_array_view(L"\x1b]133;A\aPS> \x1b]133;B\aecho hi"));
+ VERIFY_IS_TRUE(core->CommandTimelineSnapshot().entries.empty());
+
+ core->SendCharEvent(L'\r', 0, {});
+ auto running = core->CommandTimelineSnapshot();
+ VERIFY_ARE_EQUAL(size_t{ 1 }, running.entries.size());
+ VERIFY_ARE_EQUAL(std::wstring{ L"echo hi" }, running.entries[0].cachedCommandText);
+ VERIFY_ARE_EQUAL(static_cast(ExecutionResult::Running), static_cast(running.entries[0].executionResult));
+
+ connection->WriteInput(winrt_wstring_to_array_view(L"\r\nhi\r\n\x1b]133;D;0\a\x1b]133;A\aPS> \x1b]133;B\a"));
+ auto finished = core->CommandTimelineSnapshot();
+ VERIFY_ARE_EQUAL(size_t{ 1 }, finished.entries.size());
+ VERIFY_ARE_EQUAL(static_cast(ExecutionResult::Succeeded), static_cast(finished.entries[0].executionResult));
+ VERIFY_ARE_EQUAL(uint32_t{ 0 }, *finished.entries[0].trustedExitCode);
+ VERIFY_ARE_EQUAL(static_cast(ShellIntegrationCapability::Full), static_cast(finished.capability));
+
+ // An Enter on an empty integrated prompt starts no command and adds
+ // no entry.
+ core->SendCharEvent(L'\r', 0, {});
+ VERIFY_ARE_EQUAL(size_t{ 1 }, core->CommandTimelineSnapshot().entries.size());
+
+ core = nullptr;
+ connection = nullptr;
+ settings = nullptr;
+ cleanup.release();
+ }
+
void CommandTimelineTests::TrustedCompletionMapsResults()
{
CommandTimelineIndex index{ PaneOne };