feat: complete Command Timeline Phase 4 checkpoint - #30
Merged
Conversation
Adds load, copy, and jump actions to the pane-owned Command Timeline overlay introduced in Phase 2, without adding persistence, an output cache, or any path that can execute a command. - Add a pure C++ CommandTimelineActionModel that decides load, copy, and jump eligibility from the stable selected CommandId, tracks the loaded command, and advances an execution generation so a completion from a retired command is detectable and discarded. - Load the selected command onto the focused pane input with Enter or a single click. The payload is filtered for control codes only, CarriageReturnNewline is deliberately not applied, no carriage return is appended, and SendInput targets this pane connection, so the load never executes, never reads the Windows clipboard, and is never forwarded by input broadcast. - Refuse a multi-line load when the shell has not enabled bracketed paste, and require a confirming Enter above 1024 characters. Escape cancels a pending confirmation before it closes the overlay. - Add Space to jump the viewport to the selected command native mark and a per-entry context menu with copy command, copy output, and jump to output. Ctrl+C copies the selected command while the Timeline owns the keyboard. - Resolve output on demand through Terminal::ResolveCommandTimelineOutput for an explicit copy action only. Output is never cached, indexed, or retained. - Release loaded-input state when the loaded command is evicted, and keep every action bound to the stable CommandId rather than the XAML row. - Advance engineering version surfaces to 1.2.3/1.2.3.0 for checkpoint tag v1.2.3, and extend test-command-timeline.ps1 with guards that fail if the load path gains a submission character or a clipboard read. Public Latest remains v1.2.0. Search, filtering, and public commandTimeline settings remain Phase 4 scope.
Completes the in-memory Command Timeline feature surface: pane-local search, the two public settings, trustworthy shell degradation, and bounded history. No persistence, no output cache, and no telemetry are introduced. - Add pane-local search over each pane's bounded in-memory command text. The match is a literal case-insensitive substring search built on std::search and towlower; there is no regex, no fuzzy matching, no output search, and no terminal-buffer rescan. - Cap queries at 256 UTF-16 code units and truncate without leaving a lone surrogate, enforced in NormalizeCommandTimelineQuery and mirrored by MaxLength on the filter box. A query is never persisted. - Rework the navigation model to walk a filtered projection of indices while keeping stable CommandId identity. A still-matching command stays selected, a command that stops matching hands selection to the nearest surviving match, and a new command only takes the selection when it matches and the view was already following the latest command. - Route slash and Tab to the filter box and consume them before the PTY. Filter text never reaches the shell, Left/Right keep normal caret editing, and IME/TSF composition is left to the text box. Escape now clears a non-empty query before it closes the overlay. - Add the commandTimeline.enabled and commandTimeline.historyLimit global settings with defaults true and 500, a clamped 50-5000 range, JSON schema entries, and a Settings UI section under Appearance. An absent setting is not serialized back, so existing settings files need no migration. - Add bounded per-pane history with oldest-first eviction that applies to panes that already exist and to new panes. Raising the limit never resurrects an evicted command and sequence IDs are never reused. - Add four distinct empty states so an unsupported shell is never reported as simply having run no commands. - Report list item position and set size against the filtered result count, and keep localized accessible names, non-color status, High Contrast theme resources, and the Reduced Motion-safe no-animation path. - Advance engineering version surfaces to 1.2.4/1.2.4.0 for checkpoint tag v1.2.4, and extend test-command-timeline.ps1 with guards for search literalness, query bounds, settings defaults/range/schema, and the filter reading only cached command text. Public Latest remains v1.2.0. This builds on the Phase 3 branch and does not begin v1.3.0-alpha.
Phase 3 was squash-merged to main as 5fd2172. The trees are identical, so this merge only re-parents Phase 4 onto main and leaves the pull request diff as Phase 4 changes alone. # Conflicts: # CHANGELOG.md # docs/current-progress.md # docs/user/command-timeline.md # scripts/winterm/package-shell-assets.ps1 # scripts/winterm/test-command-timeline.ps1 # scripts/winterm/test-visual-progress.ps1 # scripts/winterm/test.ps1 # scripts/winterm/verify-branding.ps1 # scripts/winterm/verify-version.ps1 # shell/powershell/winTerm.Shell/winTerm.Shell.psd1 # shell/powershell/winTerm.Shell/winTerm.Shell.psm1 # shell/shared/version.json # src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest # src/cascadia/TerminalControl/ControlCore.cpp # src/cascadia/TerminalControl/ControlCore.h # src/cascadia/TerminalControl/Resources/en-US/Resources.resw # src/cascadia/TerminalControl/TermControl.cpp # src/cascadia/TerminalControl/TermControl.h # src/cascadia/UnitTests_Control/CommandTimelineTests.cpp # src/cascadia/WindowsTerminal/WindowsTerminal.rc # src/cascadia/wt/wt.rc # src/winterm-tools/winterm-shim/winterm-shim.rc # src/winterm/Branding/ReleaseMetadata.h # src/winterm/Branding/version.json # src/winterm/Workspaces/Model/WorkspaceDescriptor.h # src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Command Timeline Phase 4 — search, settings, and bounded history. This
completes the in-memory Command Timeline feature surface.
Engineering checkpoint
1.2.4/1.2.4.0, intended tagv1.2.4. Not a publicrelease — GitHub Latest and README downloads stay on v1.2.0.
Related issues
No tracking issue.
Detailed changes
Search semantics
CommandTimelineQueryMatchesis a case-insensitive literal substring search(
std::search+towlower). Deliberately not a regex and not fuzzy —test-command-timeline.ps1now fails the build ifstd::regex,regex_search,or a fuzzy matcher appears in the model (comments stripped before the check).
Filtering reads
entry.cachedCommandTextonly — the bounded 4096-characterPhase 1 cache. Output is never consulted and the terminal buffer is never
rescanned; a new command is reconciled through the existing incremental index.
Query bounds: capped at 256 UTF-16 code units by
NormalizeCommandTimelineQuery, truncating without leaving a lone surrogate.The
TextBoxcarriesMaxLength="256", and the control writes the normalizedvalue back when truncation shortens it. A query is never persisted.
Filtered projection and stable identity
The navigation model now walks
_filtered(indices into the entries span) byposition, not by raw entry index. An empty query fills
_filteredwith everyindex, so the unfiltered case takes the same path.
CommandIdretained, nothing projectedFollowing-latest additionally requires the newest command to be in the
projection, which is what stops a non-matching new command from moving the
selection. Every action still resolves through
viewState.selectedCommandId.Only
visibleCapacityrows are materialized, whatever the history size.Settings
commandTimeline.enabledtruecommandTimeline.historyLimit500Plumbed
MTSMSettings.h→GlobalAppSettings.idl→ControlProperties.h→IControlSettings.idl→TerminalSettings.cpp→ControlCore. Defaults indefaults.json; JSON schema carries type, default,minimum,maximum.An absent setting is not serialized back, so existing settings files need no
migration. An out-of-range value is accepted by the parser and clamped by
ClampCommandTimelineHistoryLimit, so the runtime value is always 50–5000rather than failing the settings load. Settings UI lives under
Settings → Appearance → Command timeline.
UpdateSettingsapplies the limit to panes that already exist; the indexconstructor applies it to new panes. Disabling hides the handle, closes an open
overlay, and makes
ToggleCommandTimelinerefuse to open.Bounded history
_applyHistoryLimiterases oldest-first on entry creation, bootstrap, andSetHistoryLimit. Lowering evicts immediately; raising never resurrects;_nextSequenceonly increases so sequence IDs are never reused;ReconcileLoadedInputreleases loaded-input state for an evicted command.Shell degradation
Four distinct states so an unsupported shell is never shown as an empty history:
WaitingForShell,ShellUnsupported,NoCommands,NoMatchingCommands. Noprompt parser, no heuristic detection, no ConPTY/VT/TextBuffer/renderer/shell
protocol change.
Input isolation
/and Tab are consumed by_tryHandleCommandTimelineKeyand never reach thePTY. Filter text never reaches the shell — the
TextBoxowns it, and_CommandTimelineSearchKeyDownclaims only Up/Down/Enter/Escape, leavingLeft/Right caret editing and IME/TSF composition alone. Escape now clears a
non-empty query before closing.
Ctrl+Taband user key bindings keep precedence(
_TryHandleKeyBindingruns first).Accessibility
Localized accessible name and placeholder on the filter box;
PositionInSet/SizeOfSetuse the filtered count; localized empty states; status never bycolor alone;
{ThemeResource}brushes for High Contrast; no storyboard orcontinuous animation; DIP geometry; overlay changes no rows/columns, pane size,
PTY size, swap-chain size, or padding.
Validation performed
Environment: Windows 11 Pro 26100, MSVC 14.44.35207, Windows SDK 10.0.26100.0.
Native builds — ran, passed (
0 Error(s)each)UnitTests_Control\Control.UnitTests.vcxprojUnitTests_SettingsModel\SettingsModel.UnitTests.vcxprojTerminalSettingsEditor\Microsoft.Terminal.Settings.Editor.vcxprojAll via
/p:Configuration=Release /p:Platform=x64 /p:SolutionDir=D:\work\winTerm\.Transitively built
TerminalCore,TerminalControlLib, andMicrosoft.Terminal.Settings.ModelLib.Unit tests — ran, passed
CommandTimelineTests(Control)Total=43, Passed=43, Failed=0WinTermCommandTimelineTests(SettingsModel)Total=4, Passed=4, Failed=0Total=73, Passed=73, Failed=0Full SettingsModel suite — 65 pre-existing failures, none from this change
Verified by reverting
MTSMSettings.h,GlobalAppSettings.idl,TerminalSettings.cpp, and the test-project entry to their Phase 3 state,rebuilding, and re-running. The failure count is identical; this change adds 4
tests and all 4 pass. The failures are host-process crashes (
0xC0000409)spread across 9 upstream classes (
DeserializationTests,MediaResourceTests,SerializationTests,CommandTests,KeyBindingsTests,NewTabMenuTests,ProfileTests,ThemeTests,TerminalSettingsTests) including tests with noconnection to global settings. Reverting only
defaults.jsonand rebuildingalso left the count at exactly 65. These are environmental on this host; the
repository's own suites (
test.ps1 -Suite Smoke,test-visual-progress.ps1)do not run the full SettingsModel suite.
Performance evidence — ran, measured
SearchStressAtMaximumHistoryLimitbuilds a full 5000-entry history, then:filters with a query matching all 5000 (projection reports 5000, materializes
exactly 20 rows =
visibleCapacity); runs 25 passes of narrow → no-result →broad filtering asserting projection and materialized row counts return to
expected values each pass; asserts cached text stays within
5000 * 4096; closes and asserts the projection is released.Measured wall clock: 1,267 ms for the whole test including TAEF host
startup. Row materialization stays at 20 regardless of the 5000-entry history.
Repository scripts — ran, passed
test-command-timeline.ps1verify-version.ps1verify-branding.ps1 -ExpectedPublisher 'CN=helloThisWorld'test-release-workflow.ps1test-ci-classification.ps1test-privacy.ps1test-visual-progress.ps1test.ps1 -Suite Smoke -Configuration Release -Platform x64Static gates — ran, passed
clang-formatover only the changed line ranges: identical modulo lineendings for every touched C++ file (two real diffs found and fixed).
git diff --check: clean.TermControl.xaml,GlobalAppearance.xaml, bothResources.resw,Package-winTerm.appxmanifest,SettingsModel.UnitTests.vcxproj: clean.version.json(both) andprofiles.schema.json: clean.defaults.json: clean, 32//comment lines preserved.Not run, with reason
test-shell-integration.ps1 -Shell All— blocked by local antivirus/AMSI,not by this change. Fails parsing
shell/powershell/winTerm.Shell/Public/Compatibility.ps1with "This scriptcontains malicious content and has been blocked by your antivirus software."
That file is byte-identical to
mainand untouched here. Needs CI or a hostwithout the AMSI block.
full unrelated upstream test suite — deliberately out of scope for a
checkpoint.
No screenshot was needed or created.
Checklist
CHANGELOG.md.Development-Changes.mdledger has been pushed with the final source SHA, link, summary, and checkpoint/release.winterm-siteasset, or no screenshot was added.winterm.exe, and Microsoft Terminal coexistence remain isolated.Source ↔ Wiki mapping
c142699a3003adBoundary
No persistent history, output cache, output search or indexing, regex or fuzzy
search, command analytics, telemetry, automatic execution, command replay, CMD
prompt parser, heuristic prompt/output detection, ConPTY/VT/TextBuffer/renderer
change, workspace or shell protocol schema change, installer, Portable
packaging, GitHub Release, Latest update,
winterm-sitechange, or screenshot.v1.3.0-alphais not started. The interfaces it can consume directly areCommandTimelineNavigationModel::SetQuery/Query/FilteredCount,CommandTimelineActionModel,CommandTimelineEmptyState,ClampCommandTimelineHistoryLimit,CommandTimelineIndex::SetHistoryLimit, andControlCore::FilterCommandTimeline/PrepareCommandTimelineAction.