Skip to content

feat: complete Command Timeline Phase 3 checkpoint - #29

Merged
HelloThisWorld merged 1 commit into
mainfrom
feature/command-timeline-v1.3.0
Aug 3, 2026
Merged

feat: complete Command Timeline Phase 3 checkpoint#29
HelloThisWorld merged 1 commit into
mainfrom
feature/command-timeline-v1.3.0

Conversation

@HelloThisWorld

Copy link
Copy Markdown
Owner

Summary

Command Timeline Phase 3 — entry actions. The Phase 2 overlay was read-only;
this makes it actionable. You can load an earlier command back onto the input
line, copy a command or its output, and jump to a command's output.

Engineering checkpoint 1.2.3 / 1.2.3.0, intended tag v1.2.3. Not a public
release
— GitHub Latest and README downloads stay on v1.2.0.

Related issues

No tracking issue.

Detailed changes

Load semantics — a load can never execute

ControlCore::LoadCommandTimelineCommand is the only load path:

  • Payload is filtered with FilterStringForPaste(text, ControlCodes).
    CarriageReturnNewline is deliberately not applied — converting a newline
    to a carriage return would submit the command instead of loading it.
  • No carriage return is ever appended.
  • Bracketed paste wrapping is applied when the shell has it enabled.
  • SendInput targets this pane's connection only, so the Windows clipboard
    is never read and input broadcast cannot forward the load to another pane.

test-command-timeline.ps1 now fails the build if that function ever gains
CarriageReturnNewline, a carriage-return append, or a clipboard read.

Multi-line and large loads

Condition Result
Empty command text CommandTextUnavailable
Multi-line, bracketed paste off MultilineUnsafe — refused
Multi-line, bracketed paste on Loads literally
Over 1024 characters ConfirmationRequired — Enter again to confirm

Refusing multi-line without bracketed paste is stricter than clipboard paste,
which only warns. Same reasoning as the upstream GH#13014 note: unbracketed line
breaks are indistinguishable from the user pressing Enter.

Stable identity

Every action resolves through viewState.selectedCommandId — a stable
pane-scoped CommandId — never a XAML row index. ActionCopyResolvesStableCommandIdNotRowIndex
covers an eviction shifting every row index by one between two preparations.

Execution generation / late-completion race

CommandTimelineViewState gains loadedCommandId alongside the existing
loadedIntoInput and executionGeneration. NotifyLoaded records and bumps;
NotifyExecutionStarted fires on OSC 133 CommandStart, clears loaded state and
bumps again; IsCurrentGeneration then reports a retired generation as stale.
ResolveCommandTimelineOutput checks the generation before reading the buffer.

Output stays uncached

Output is read from the buffer only for an explicit copy-output action, via
Terminal::ResolveCommandTimelineOutput. Nothing is stored — there is still no
output field anywhere in the model, and the script guard enforces that.

Keys

Key Behavior
Enter / single click Load selected command (never executes)
Space Jump viewport to that command's output
Ctrl+C Copy selected command text
Escape Cancel pending confirmation, otherwise close

Hover still only moves the selection. Ctrl+Tab toggle and user-defined key
bindings keep precedence (_TryHandleKeyBinding runs first).

Validation performed

Environment: Windows 11 Pro 26100, MSVC 14.44.35207, Windows SDK 10.0.26100.0.

Native build — ran, passed

MSBuild src\cascadia\UnitTests_Control\Control.UnitTests.vcxproj
  /p:Configuration=Release /p:Platform=x64 /p:SolutionDir=D:\work\winTerm\ /m

0 Error(s), 7 warnings (all pre-existing PRI263 resource warnings unrelated
to this change). Elapsed 9m04s. Built TerminalCore, TerminalControlLib, and
UnitTests_Control.

Unit tests — ran, passed

TE.exe bin\x64\Release\UnitTests_Control\Control.Unit.Tests.dll /name:*CommandTimelineTests*

Total=31, Passed=31, Failed=0 — including the 10 new Action* tests:
load preparation, missing command text, multi-line refusal, large-load
confirmation, generation tracking, late-completion detection, eviction release,
stable-identity resolution, and output availability.

Repository scripts — ran, passed

Command Result
test-command-timeline.ps1 PASS (extended with Phase 3 guards)
verify-version.ps1 PASS — 1.2.3 / 1.2.3.0 / v1.2.3, Latest still 1.2.0
verify-branding.ps1 -ExpectedPublisher 'CN=helloThisWorld' PASS
test-release-workflow.ps1 PASS
test-ci-classification.ps1 PASS
test-visual-progress.ps1 PASS — Total=52, Passed=52, Failed=0
test-privacy.ps1 PASS
test.ps1 -Suite Smoke -Configuration Release -Platform x64 PASS

Static gates — ran, passed

  • clang-format over only the changed line ranges (repo .clang-format):
    identical modulo line endings for every touched C++ file.
  • git diff --check: clean.
  • PowerShell AST parse of all 8 touched scripts: clean.
  • XML parse of TermControl.xaml, Resources.resw, Package-winTerm.appxmanifest: clean.
  • Strict JSON parse of version.json (both): clean.

Not run, with reason

  • test-shell-integration.ps1 -Shell Allblocked by local antivirus/AMSI,
    not by this change. It fails parsing shell/powershell/winTerm.Shell/Public/Compatibility.ps1
    with "This script contains malicious content and has been blocked by your
    antivirus software."
    That file is byte-identical to main and is untouched
    here; git diff main --quiet on it reports no change. Needs to run in CI or on
    a host without the AMSI block.
  • Full Release build, installer, Portable packaging, delivery workflow, and the
    full upstream test suite — deliberately out of scope for a checkpoint.

No screenshot was needed or created.

Checklist

  • The change is focused and does not include unrelated formatting.
  • Tests were added or updated where appropriate.
  • All tests claimed above actually ran and passed.
  • User-facing behavior and limitations are documented in this repository.
  • This source/docs commit updates the root CHANGELOG.md.
  • The Wiki Development-Changes.md ledger has been pushed with the final source SHA, link, summary, and checkpoint/release.
  • Documentation screenshots reuse a suitable existing sanitized winterm-site asset, or no screenshot was added.
  • Version or schema changes include compatibility and migration notes.
  • Package identity, winterm.exe, and Microsoft Terminal coexistence remain isolated.
  • No command text, terminal output, clipboard content, credentials, or private paths are logged.
  • New source and script files contain the appropriate MIT license header.
  • I did not include generated build output, secrets, certificates, or local absolute paths.

Source ↔ Wiki mapping

Source commit Wiki commit Wiki pages
08afcea 323473d Development-Changes, Changelog, Home

Boundary

No persistence, no output cache, no search or filtering, no telemetry, no
automatic execution, no ConPTY/VT/TextBuffer/renderer changes, no workspace or
shell protocol schema change, no installer, no Portable packaging, no GitHub
Release, no Latest update, no winterm-site change, no screenshot.

Phase 4 (search, filtering, commandTimeline.enabled / commandTimeline.historyLimit,
Settings UI) builds on this and is not included here.

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.
@HelloThisWorld
HelloThisWorld marked this pull request as ready for review August 3, 2026 15:56
@HelloThisWorld
HelloThisWorld merged commit 5fd2172 into main Aug 3, 2026
5 checks passed
@HelloThisWorld
HelloThisWorld deleted the feature/command-timeline-v1.3.0 branch August 3, 2026 15:56
HelloThisWorld added a commit that referenced this pull request Aug 3, 2026
Phase 3 was squash-merged to main as 5fd2172 through pull request #29 while
Phase 4 was in progress, so the Phase 4 base branch is main rather than the
Phase 3 feature branch.
HelloThisWorld added a commit that referenced this pull request Aug 3, 2026
* Complete Command Timeline Phase 3 entry actions

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.

* Complete Command Timeline Phase 4 stabilization

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.

* docs: record Phase 3 merge to main in Phase 4 checkpoint notes

Phase 3 was squash-merged to main as 5fd2172 through pull request #29 while
Phase 4 was in progress, so the Phase 4 base branch is main rather than the
Phase 3 feature branch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant