From 22ff28c9d2be910cf3c4a4b1d9c71641e8e9d394 Mon Sep 17 00:00:00 2001 From: Timothy Bruce Date: Thu, 15 Oct 2026 18:53:28 -0400 Subject: [PATCH] build: prepare DCurses 2.5.1 on Terminal 1.29.1 --- .github/workflows/release.yaml | 20 +++------ .gitignore | 2 + CHANGELOG.md | 7 ++++ Icod.DCurses-Development-Roadmap.md | 15 +++---- Icod.DCurses.csproj | 8 ++-- README.md | 18 ++++++++ docs/releases/2.5.1.md | 41 +++++++++++++++++++ packaging/Assert-ReleaseNotes.ps1 | 26 ++++++++++++ packaging/Invoke-Build.ps1 | 1 + .../Icod.DCurses.RasterAtlas.Sample/README.md | 8 ++-- samples/README.md | 10 ++++- .../Icod.DCurses.Tests.csproj | 2 +- .../src/CursesRichInputAcceptanceTests.cs | 41 ++++++++++++++++++- .../PublicTwoFiveDevelopmentIdentityTests.cs | 8 ++-- .../src/PublicTwoFiveReleaseIdentityTests.cs | 31 +++++++++++++- .../PublicTwoFourDevelopmentIdentityTests.cs | 8 ++-- .../src/PublicTwoFourPackageGateTests.cs | 14 +++---- tools/package-smoke/README.md | 2 +- tools/package-verifier/Program.cs | 2 +- 19 files changed, 212 insertions(+), 52 deletions(-) create mode 100644 docs/releases/2.5.1.md create mode 100644 packaging/Assert-ReleaseNotes.ps1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d21c4470e..0736d2fa8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,7 +23,6 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} prerelease: ${{ steps.version.outputs.prerelease }} - terminal_version: ${{ steps.version.outputs.terminal_version }} steps: - uses: actions/checkout@v4 with: @@ -70,7 +69,6 @@ jobs: "version=$tagVersion" >> $env:GITHUB_OUTPUT "prerelease=$($tagVersion.Contains('-').ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT - "terminal_version=$terminalVersion" >> $env:GITHUB_OUTPUT runtime: needs: metadata @@ -180,6 +178,7 @@ jobs: permissions: contents: write steps: + - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: ${{ env.PACKAGE_ARTIFACT }} @@ -202,19 +201,12 @@ jobs: GH_REPO: ${{ github.repository }} run: | $version = '${{ needs.metadata.outputs.version }}' - $terminalVersion = '${{ needs.metadata.outputs.terminal_version }}' $assets = @(Get-ChildItem -LiteralPath 'artifacts/release-assets' -File | Sort-Object Name | ForEach-Object { $_.FullName }) - $notes = @' - ## Installation - - ```text - dotnet add package Icod.DCurses --version {0} - ``` - - `Icod.DCurses` targets `net8.0`, `net9.0`, and `net10.0`. Its runtime package dependencies are - `Icod.Terminal {1}` directly; `Icod.TermInfo` is resolved transitively through Terminal. - '@ -f $version, $terminalVersion - $arguments = @('release', 'create', $env:GITHUB_REF_NAME, '--verify-tag', '--title', "Icod.DCurses $version", '--generate-notes', '--notes', $notes) + $notesPath = "docs/releases/$version.md" + if (-not (Test-Path -LiteralPath $notesPath -PathType Leaf)) { + throw "Curated release notes not found at '$notesPath'." + } + $arguments = @('release', 'create', $env:GITHUB_REF_NAME, '--verify-tag', '--title', "Icod.DCurses $version", '--notes-file', $notesPath) if ('true' -eq '${{ needs.metadata.outputs.prerelease }}') { $arguments += '--prerelease' $arguments += '--latest=false' diff --git a/.gitignore b/.gitignore index d5a18deed..ab7f90218 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ mono_crash.* [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ +!docs/releases/ +!docs/releases/*.md [Dd]ebug/x64/ [Dd]ebugPublic/x64/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d333e92..50ccdded4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable `Icod.DCurses` release-line changes are summarized here. Detailed tranche evidence, API fingerprints, roadmaps, and release qualification records remain under `docs/` and the versioned roadmap files. +## 2.5.1 — Raw shortcut input compatibility + +- Updated the sole direct runtime dependency to stable `Icod.Terminal 1.29.1`. +- Qualified raw-mode `Ctrl+C` and `Ctrl+Z` delivery through the DCurses event stream as `CursesKey.Character` events carrying the `Control` modifier, allowing applications to bind copy and undo without those bytes being consumed by host signal processing. +- Inherited Terminal's corrected legacy Alt-prefix handling, including `Alt+F`, through the existing DCurses input adapter. +- Preserved the 2.5.0 public contract, `AssemblyVersion 2.0.0.0`, C# 13, .NET 8/9/10 targets, retained presentation behavior, and package dependency shape. Applications that want host signal behavior should continue using the default `CBreak` mode; applications that own `Ctrl+C`/`Ctrl+Z` shortcuts must select `Raw`. + ## 2.5.0 — Color-surface qualification (stable-source candidate) - Qualified existing independent foreground/background styles through logical mutation, wide-cell repair, pads, viewports, opaque and blank-transparent panels, style-only refresh, capability fallback, and resize/invalidation/lifecycle repaint. diff --git a/Icod.DCurses-Development-Roadmap.md b/Icod.DCurses-Development-Roadmap.md index 8cfff6505..79de587dc 100644 --- a/Icod.DCurses-Development-Roadmap.md +++ b/Icod.DCurses-Development-Roadmap.md @@ -4,17 +4,17 @@ **Repository:** `https://github.com/uniblab/Icod.DCurses`\ **Published compatibility floor:** `1.0.0`\ **Published releases:** [GitHub Releases](https://github.com/uniblab/Icod.DCurses/releases) and [NuGet](https://www.nuget.org/packages/Icod.DCurses/)\ -**Current stable source candidate:** `2.5.0` on PR #38; not merged, tagged or published\ -**Latest published stable package:** `2.4.0`\ +**Current stable source candidate:** `2.5.1` maintenance candidate on the active pull request\ +**Latest published stable package:** `2.5.0`\ **Current development assembly version:** `2.0.0.0`\ -**Current development runtime dependency:** direct stable `Icod.Terminal 1.29.0` only; TermInfo remains transitive\ +**Current development runtime dependency:** direct stable `Icod.Terminal 1.29.1` only; TermInfo remains transitive\ **Stable 2.4 direct runtime dependency:** `Icod.Terminal 1.29.0`; no direct `Icod.TermInfo` reference\ **Target frameworks:** `net8.0`; `net9.0`; `net10.0`\ **Configurations:** `Debug`; `Staging`; `Release`\ -**Active feature track:** 2.5.0 color-surface hardening for Icod.Widgets and dedit; stable source qualified on PR #38\ -**Status:** Version 2.4.0 is the latest published stable package. Version 2.5.0 stable source has completed T2500–T2507, including unchanged public API, public-only color sample, package consumer, full Release gate, and successful RC/stable-source Windows/Linux/macOS x64/ARM64 PR matrices. Live terminal color visuals remain unverified; the maintainer controls merge, tag and publication. See the [2.5 roadmap](Icod.DCurses-2.5.0-Development-Roadmap.md) and [T2507 release gate](docs/T2507-2.5.0-Stable-Source-Gate.md). +**Active feature track:** 2.5.1 maintenance update for Terminal 1.29.1 shortcut-input compatibility\ +**Status:** Version 2.5.0 is published. Version 2.5.1 updates the sole direct Terminal dependency, qualifies raw `Ctrl+C`/`Ctrl+Z` delivery through DCurses, and preserves the 2.5 public API and assembly identity. The maintainer controls merge, tag and publication. See the [2.5 roadmap](Icod.DCurses-2.5.0-Development-Roadmap.md), [T2507 release gate](docs/T2507-2.5.0-Stable-Source-Gate.md), and [2.5.1 curated release notes](docs/releases/2.5.1.md). -**Planning snapshot:** 2026-10-12 +**Planning snapshot:** 2026-10-15 --- @@ -71,7 +71,8 @@ Historical 1.0-1.6 roadmaps, tranche records, public-API baselines/fingerprints, | `2.3.0` | First-class `CursesRasterAtlas` coordination for cell-aligned tile presentation | Published 2026-10-09 | | `2.3.1` | Package documentation and release-state correction | Stable maintenance patch; no runtime or public API changes | | `2.4.0` | Widgets/dedit foundations: Terminal 1.29 clipboard/cursor facades, qualified visible projection and capture | Published 2026-10-12 | -| `2.5.0` | Complete foreground/background color-surface retention, composition, damage and fallback qualification | Stable source qualified on PR #38; merge/tag/publication pending | +| `2.5.0` | Complete foreground/background color-surface retention, composition, damage and fallback qualification | Published 2026-10-14 | +| `2.5.1` | Terminal 1.29.1 raw shortcut-input compatibility | Maintenance candidate; merge/tag/publication pending | | Later | Higher-level `Icod.Widgets` themes and separately evidenced sprite/physical-placement work | Separate repositories/tracks; not implicitly in 2.5 | The post-1.0 progression is intentionally cumulative: diff --git a/Icod.DCurses.csproj b/Icod.DCurses.csproj index 359c432fa..ed2b9c753 100644 --- a/Icod.DCurses.csproj +++ b/Icod.DCurses.csproj @@ -34,16 +34,16 @@ Icod.DCurses Icod.DCurses Debug;Staging;Release - 2.5.0 + 2.5.1 2.0.0.0 - 2.5.0 + 2.5.1 Icod.DCurses Icod.DCurses Timothy J. Bruce Managed, cross-platform curses-style terminal UI for .NET with retained Unicode text, independently styled foreground/background color surfaces, viewports, panels, mixed media, deterministic interaction routing, bounded clipboard access, and scoped cursor styles. - 2.5 qualifies existing foreground/background color retention, opaque-panel styled blanks, damage-aware rendition refresh, capability fallback, and lifecycle repaint across .NET 8/9/10; adds a public-only color sample and package consumer. No public API changes. Existing bounded clipboard and cursor-style APIs remain. The sole direct runtime dependency is stable Icod.Terminal 1.29.0. + 2.5.1 updates the sole direct runtime dependency to Icod.Terminal 1.29.1 so raw-mode applications reliably receive Ctrl+C and Ctrl+Z as Control-modified character events and legacy Alt+character input remains available through the DCurses event stream. No public API or AssemblyVersion change. README.md icon.png https://github.com/uniblab/Icod.DCurses @@ -116,6 +116,6 @@ - + diff --git a/README.md b/README.md index 2f7e1e827..c5c641c9f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,24 @@ The application decides how to interpret `inputEvent`, when to refresh again, and when to exit. DCurses does not start a callback dispatcher or a second input reader. +## Input modes and application shortcuts + +`CursesSessionOptions.InputMode` defaults to `CursesInputMode.CBreak`, which +keeps host signal processing enabled. Select `CursesInputMode.Raw` when the +application must receive `Ctrl+C`, `Ctrl+Z`, or similar control chords itself: + +```csharp +CursesSessionOptions options = new() { + InputMode = CursesInputMode.Raw +}; +await using CursesSession session = await CursesSession.OpenAsync( options ); +``` + +In raw mode, `Ctrl+C` and `Ctrl+Z` are delivered as `CursesKey.Character` +events with `CursesKeyModifiers.Control`; the application decides whether they +mean copy, undo, exit, or another command. Use `CBreak` when the host should +retain its normal interrupt and suspend handling. + ## Color surfaces and fallback Each `CursesCell` carries a complete `CursesStyle`: independently requested diff --git a/docs/releases/2.5.1.md b/docs/releases/2.5.1.md new file mode 100644 index 000000000..c4ec45720 --- /dev/null +++ b/docs/releases/2.5.1.md @@ -0,0 +1,41 @@ +# Icod.DCurses 2.5.1 + +Icod.DCurses 2.5.1 is a focused input-compatibility maintenance release. It +updates the sole direct runtime dependency to Icod.Terminal 1.29.1 while +preserving the complete 2.5 public API and retained-presentation behavior. + +## What changed + +- Raw-mode sessions now reliably surface `Ctrl+C` and `Ctrl+Z` through the + DCurses event stream as `CursesKey.Character` events with + `CursesKeyModifiers.Control`. +- Legacy Alt-prefix input, including `Alt+F`, benefits from Terminal 1.29.1's + corrected decoding through the existing DCurses adapter. +- Permanent byte-level acceptance coverage protects `Ctrl+C`, `Ctrl+Z`, and + legacy `Alt+F` delivery from the Terminal session through + `CursesSession.ReadEventAsync`. + +## Choosing an input mode + +DCurses continues to default to `CursesInputMode.CBreak`. That mode keeps host +signal processing enabled, so applications that want conventional interrupt and +suspend behavior do not need to change anything. + +Applications that use `Ctrl+C` for copy, `Ctrl+Z` for undo, or otherwise own +control-key policy should open the session with `CursesInputMode.Raw`. In raw +mode, the application is also responsible for providing its own exit and +job-control policy. + +## Compatibility + +- No public API changes. +- `AssemblyVersion` remains `2.0.0.0`. +- Target frameworks remain .NET 8, .NET 9, and .NET 10. +- The only direct runtime dependency remains `Icod.Terminal`, now at 1.29.1; + Icod.TermInfo remains transitive. + +## Install + +```text +dotnet add package Icod.DCurses --version 2.5.1 +``` diff --git a/packaging/Assert-ReleaseNotes.ps1 b/packaging/Assert-ReleaseNotes.ps1 new file mode 100644 index 000000000..76c9bf7c5 --- /dev/null +++ b/packaging/Assert-ReleaseNotes.ps1 @@ -0,0 +1,26 @@ +param( + [ValidateSet('Debug', 'Staging', 'Release')] + [string]$Configuration = 'Release' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$repositoryRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) +Import-Module (Join-Path $PSScriptRoot 'RepositoryTools.psm1') -Force + +$projectPath = Join-Path $repositoryRoot 'Icod.DCurses.csproj' +$version = Get-MSBuildProperty -ProjectPath $projectPath -Name 'PackageVersion' -Configuration $Configuration +if ($version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?$') { + throw "Invalid DCurses package version '$version'." +} + +$notesPath = Join-Path $repositoryRoot "docs/releases/$version.md" +if (-not (Test-Path -LiteralPath $notesPath -PathType Leaf)) { + throw "Package version '$version' requires curated notes at '$notesPath' before tagging." +} +if ([string]::IsNullOrWhiteSpace([System.IO.File]::ReadAllText($notesPath))) { + throw "Curated notes at '$notesPath' are empty." +} + +Write-Host "Curated release notes found for Icod.DCurses $version." diff --git a/packaging/Invoke-Build.ps1 b/packaging/Invoke-Build.ps1 index 5229e7aeb..43a707468 100644 --- a/packaging/Invoke-Build.ps1 +++ b/packaging/Invoke-Build.ps1 @@ -56,6 +56,7 @@ function Invoke-Pack { function Invoke-Validate { Write-Host '' Write-Host "=== Validate ($Configuration) ===" + & (Join-Path $repositoryRoot 'packaging/Assert-ReleaseNotes.ps1') -Configuration $Configuration if ($isWindowsHost) { & cmd /c (Join-Path $repositoryRoot '.github/scripts/verify-release-package.cmd') $artifactDirectory $Configuration } else { diff --git a/samples/Icod.DCurses.RasterAtlas.Sample/README.md b/samples/Icod.DCurses.RasterAtlas.Sample/README.md index dc8cd59fb..d55f2ef5c 100644 --- a/samples/Icod.DCurses.RasterAtlas.Sample/README.md +++ b/samples/Icod.DCurses.RasterAtlas.Sample/README.md @@ -9,7 +9,7 @@ resize recreation and a text fallback driven by the same model. ## Current release boundary -The current 2.4.0 source consumes stable `Icod.Terminal 1.29.0` through DCurses. +The current 2.5.1 source consumes stable `Icod.Terminal 1.29.1` through DCurses. Version 2.4 adds bounded clipboard and cursor-style session mechanisms without changing this sample's retained raster contract. The maintainer has accepted the sample's ATLAS, FRAME, and TEXT behavior, @@ -99,7 +99,7 @@ the pixels correctly. Record that separately with the T2308/T2310 live checklist ## Live acceptance and known limits -The sample consumes stable Terminal 1.29.0 through DCurses, including bounded +The sample consumes stable Terminal 1.29.1 through DCurses, including bounded persistent-transfer deadlines, immediate Linux/macOS byte input, Kitty DA1 compatibility, newline-safe cursor planning, and coalesced Sixel screen writes. The public mode indicator is the acceptance authority: `ATLAS`, `FRAME`, or @@ -187,8 +187,8 @@ dotnet list Icod.DCurses.csproj package dotnet run --project samples/Icod.DCurses.RasterAtlas.Sample/Icod.DCurses.RasterAtlas.Sample.csproj -c Release -f net10.0 ``` -For the current 2.4.0 source, confirm the resolved direct package is -`Icod.Terminal 1.29.0`. Use +For the current 2.5.1 source, confirm the resolved direct package is +`Icod.Terminal 1.29.1`. Use `-- --raster` to exercise FRAME directly and `-- --text` to force TEXT. Record the mode indicator along with rendering behavior; a fallback is a controlled result, not evidence that another mode rendered. diff --git a/samples/README.md b/samples/README.md index 5642ee034..5b4b0eded 100644 --- a/samples/README.md +++ b/samples/README.md @@ -3,8 +3,8 @@ The repository contains fourteen executable samples. They are intentionally separate so the minimal session lifecycle stays easy to copy without mixing it with the interactive and acceptance-focused showcases. All sample projects target `net8.0`, `net9.0`, and `net10.0` and consume the -repository `Icod.DCurses` project. Icod.DCurses 2.5.0 declares only stable -`Icod.Terminal 1.29.0` directly; Terminal may restore TermInfo transitively. See +repository `Icod.DCurses` project. Icod.DCurses 2.5.1 declares only stable +`Icod.Terminal 1.29.1` directly; Terminal may restore TermInfo transitively. See the [changelog](../CHANGELOG.md) for historical package dependencies and the [2.0 migration guide](../docs/2.0-Migration-Guide.md) when upgrading an external application from the 1.x public surface. @@ -118,6 +118,12 @@ The sample uses public `CursesTextLayout` source positions and geometry for Unic Typing replaces the active selection, and Backspace/Delete remove it at complete text-element boundaries. `Ctrl+W` toggles wrap; `Ctrl+T` starts/stops keyboard selection; `Ctrl+C` and `Ctrl+V` copy and paste; `Ctrl+G` or `Ctrl+K Ctrl+G` opens the 1-based record prompt. Within that explicit prompt scope, `Ctrl+K Ctrl+C` or `Escape` cancels and `Enter` accepts. Arrows navigate; `PageUp`/`PageDown` jump records; `Home`/`End` jump within the current record; typing, `Tab`, `Enter`, `Backspace` and `Delete` edit. `Escape` exits from the document context. The interaction controller registers the lowercase modern and uppercase traditional character identities for each Control shortcut. +The editor sample explicitly opens `CursesInputMode.Raw`, so control bytes such +as `Ctrl+C` and `Ctrl+Z` reach the application as Control-modified character +events. The sample binds `Ctrl+C`; a full editor can bind `Ctrl+Z` to undo. Use +the default `CBreak` mode instead when the host should retain interrupt and +suspend processing. + Primary-button clicks place the caret in the document. Dragging extends the selection and remains captured when the pointer leaves the document; releasing the button, losing focus, opening the prompt, resizing, or disposing the controller ends capture. The final document column is an independently hit-tested scrollbar: clicking or dragging it maps its row onto the synthetic document without turning `Tab` into focus traversal. The editor requests Raw input so the host delivers `Ctrl+C` to the command router instead of converting it into an interrupt; use `Escape` for orderly exit. Clipboard commands explicitly use the session OSC 52 boundary. The sample keeps a private application fallback when the host clipboard is unavailable, denied, malformed, or times out; a timeout is inconclusive and is not treated as proof that the host lacks clipboard support. Host consent and policy remain terminal concerns. The visible caret remains enabled, and the sample requests a steady bar cursor through a scoped lease; unsupported or inconclusive style negotiation leaves the host style unchanged. Resize recomputes the document, status, prompt and interaction regions and invalidates any live prefix. Minimum size is 30 columns by 6 rows. diff --git a/tests/Icod.DCurses.Tests/Icod.DCurses.Tests.csproj b/tests/Icod.DCurses.Tests/Icod.DCurses.Tests.csproj index 47b01275d..bf3bf37b8 100644 --- a/tests/Icod.DCurses.Tests/Icod.DCurses.Tests.csproj +++ b/tests/Icod.DCurses.Tests/Icod.DCurses.Tests.csproj @@ -39,7 +39,7 @@ - + diff --git a/tests/Icod.DCurses.Tests/src/CursesRichInputAcceptanceTests.cs b/tests/Icod.DCurses.Tests/src/CursesRichInputAcceptanceTests.cs index 0cc42be22..4f92b93d4 100644 --- a/tests/Icod.DCurses.Tests/src/CursesRichInputAcceptanceTests.cs +++ b/tests/Icod.DCurses.Tests/src/CursesRichInputAcceptanceTests.cs @@ -30,6 +30,38 @@ namespace Icod.DCurses.Tests; /// Acceptance coverage for rich terminal input through the DCurses facade. /// public sealed class CursesRichInputAcceptanceTests { + [Fact] + public async Task RawControlShortcutsAndLegacyAltFReachTheCursesEventStream() { + TerminalSession terminalSession = await OpenTerminalSessionAsync( + new RecordingOutput(), + new ScriptedInput( [ 0x03, 0x1A, 0x1B, (byte)'f' ] ), + CreateRichInputTerminal(), + TerminalInputMode.Raw + ); + await using CursesSession session = await CursesSession.OpenAsync( + terminalSession, + NoPresentationOptions( CursesInputMode.Raw ) + ); + + CursesInputEvent copy = await ReadInputAsync( session ); + Assert.Equal( CursesInputEventKind.Key, copy.Kind ); + Assert.Equal( CursesKey.Character, copy.Key ); + Assert.Equal( new Rune( 'C' ), copy.Character ); + Assert.Equal( CursesKeyModifiers.Control, copy.Modifiers ); + + CursesInputEvent undo = await ReadInputAsync( session ); + Assert.Equal( CursesInputEventKind.Key, undo.Kind ); + Assert.Equal( CursesKey.Character, undo.Key ); + Assert.Equal( new Rune( 'Z' ), undo.Character ); + Assert.Equal( CursesKeyModifiers.Control, undo.Modifiers ); + + CursesInputEvent fileMenu = await ReadInputAsync( session ); + Assert.Equal( CursesInputEventKind.Key, fileMenu.Kind ); + Assert.Equal( CursesKey.Character, fileMenu.Key ); + Assert.Equal( new Rune( 'f' ), fileMenu.Character ); + Assert.Equal( CursesKeyModifiers.Alt, fileMenu.Modifiers ); + } + [Fact] public async Task RichInputFamiliesAndModifiedKeysUseSingleCursesEventStream() { string inputText = @@ -145,8 +177,11 @@ CursesSession session return ( await session.ReadEventAsync( timeout.Token ) ).Input!; } - private static CursesSessionOptions NoPresentationOptions() { + private static CursesSessionOptions NoPresentationOptions( + CursesInputMode inputMode = CursesInputMode.CBreak + ) { return new CursesSessionOptions { + InputMode = inputMode, UseAlternateScreen = false, EnableKeypad = false, HideCursor = false @@ -179,7 +214,8 @@ private static TerminalDescription CreateRichInputTerminal() { private static ValueTask OpenTerminalSessionAsync( RecordingOutput output, ITerminalInput input, - TerminalDescription terminal + TerminalDescription terminal, + TerminalInputMode inputMode = TerminalInputMode.CBreak ) { ArgumentNullException.ThrowIfNull( output ); ArgumentNullException.ThrowIfNull( input ); @@ -192,6 +228,7 @@ TerminalDescription terminal input, output, new TerminalSessionOptions { + InputMode = inputMode, TerminalOverride = terminal, ConfigureOutput = false, ObserveLifecycleEvents = false diff --git a/tests/Icod.DCurses.Tests/src/PublicTwoFiveDevelopmentIdentityTests.cs b/tests/Icod.DCurses.Tests/src/PublicTwoFiveDevelopmentIdentityTests.cs index a6a9b8a2d..3e02f0e3d 100644 --- a/tests/Icod.DCurses.Tests/src/PublicTwoFiveDevelopmentIdentityTests.cs +++ b/tests/Icod.DCurses.Tests/src/PublicTwoFiveDevelopmentIdentityTests.cs @@ -25,10 +25,10 @@ namespace Icod.DCurses.Tests; using System.Xml.Linq; using Xunit; -/// Guards the 2.5 preview identity and frozen color foundation. +/// Guards the 2.5 maintenance identity and frozen color foundation. public sealed class PublicTwoFiveDevelopmentIdentityTests { [Fact] - public void PreviewIdentityPreservesColorFoundationAndTerminalBoundary() { + public void MaintenanceIdentityPreservesColorFoundationAndTerminalBoundary() { XDocument production = XDocument.Load( Path.Combine( FindRepositoryRoot(), "Icod.DCurses.csproj" ) ); @@ -40,7 +40,7 @@ public void PreviewIdentityPreservesColorFoundationAndTerminalBoundary() { static current => "PackageReference" == current.Name.LocalName ); Assert.Equal( "Icod.Terminal", reference.Attribute( "Include" )?.Value ); - Assert.Equal( "1.29.0", reference.Attribute( "Version" )?.Value ); + Assert.Equal( "1.29.1", reference.Attribute( "Version" )?.Value ); Assert.DoesNotContain( production.Descendants(), static current => "PackageReference" == current.Name.LocalName @@ -84,7 +84,7 @@ [ typeof( byte ), typeof( byte ), typeof( byte ) ] )?.PropertyType ); string version = GetSingleValue( production, "Version" ); - Assert.Contains( version, new[] { "2.5.0-alpha.1", "2.5.0-rc.1", "2.5.0" } ); + Assert.Equal( "2.5.1", version ); Assert.Equal( version, GetSingleValue( production, "PackageVersion" ) ); } diff --git a/tests/Icod.DCurses.Tests/src/PublicTwoFiveReleaseIdentityTests.cs b/tests/Icod.DCurses.Tests/src/PublicTwoFiveReleaseIdentityTests.cs index 578f468a9..12bdbcdf1 100644 --- a/tests/Icod.DCurses.Tests/src/PublicTwoFiveReleaseIdentityTests.cs +++ b/tests/Icod.DCurses.Tests/src/PublicTwoFiveReleaseIdentityTests.cs @@ -38,6 +38,35 @@ public void CandidateIdentityIsExplicitAndSynchronized() { .Where( element => element.Name.LocalName is "Version" or "PackageVersion" ) .Select( element => element.Value ) .ToArray(); - Assert.Equal( [ "2.5.0", "2.5.0" ], versions ); + Assert.Equal( [ "2.5.1", "2.5.1" ], versions ); + } + + [Fact] + public void CandidateHasCuratedReleaseNotesAndPublicationUsesThem() { + DirectoryInfo? root = new( AppContext.BaseDirectory ); + while ( root is not null && !File.Exists( Path.Combine( root.FullName, "Icod.DCurses.csproj" ) ) ) { + root = root.Parent; + } + Assert.NotNull( root ); + + string notes = File.ReadAllText( Path.Combine( + root.FullName, + "docs", + "releases", + "2.5.1.md" + ) ); + Assert.Contains( "Ctrl+C", notes, StringComparison.Ordinal ); + Assert.Contains( "Ctrl+Z", notes, StringComparison.Ordinal ); + Assert.Contains( "Alt+F", notes, StringComparison.Ordinal ); + Assert.Contains( "Icod.Terminal 1.29.1", notes, StringComparison.Ordinal ); + + string workflow = File.ReadAllText( Path.Combine( + root.FullName, + ".github", + "workflows", + "release.yaml" + ) ); + Assert.Contains( "--notes-file", workflow, StringComparison.Ordinal ); + Assert.Contains( "docs/releases/$version.md", workflow, StringComparison.Ordinal ); } } diff --git a/tests/Icod.DCurses.Tests/src/PublicTwoFourDevelopmentIdentityTests.cs b/tests/Icod.DCurses.Tests/src/PublicTwoFourDevelopmentIdentityTests.cs index 29ca315dd..ce32d3c29 100644 --- a/tests/Icod.DCurses.Tests/src/PublicTwoFourDevelopmentIdentityTests.cs +++ b/tests/Icod.DCurses.Tests/src/PublicTwoFourDevelopmentIdentityTests.cs @@ -25,10 +25,10 @@ namespace Icod.DCurses.Tests; using System.Xml.Linq; using Xunit; -/// Guards the approved 2.4 stable-source identity and dependency boundary. +/// Guards the published 2.4 fingerprint and current dependency boundary. public sealed class PublicTwoFourDevelopmentIdentityTests { [Fact] - public void PublishedIdentityPreservesStableTerminalBoundary() { + public void PublishedFingerprintAndCurrentSourcePreserveTerminalBoundary() { XDocument production = XDocument.Load( Path.Combine( FindRepositoryRoot(), "Icod.DCurses.csproj" ) ); @@ -61,7 +61,7 @@ public void PublishedIdentityPreservesStableTerminalBoundary() { static current => "PackageReference" == current.Name.LocalName ); Assert.Equal( "Icod.Terminal", reference.Attribute( "Include" )?.Value ); - Assert.Equal( "1.29.0", reference.Attribute( "Version" )?.Value ); + Assert.Equal( "1.29.1", reference.Attribute( "Version" )?.Value ); Assert.DoesNotContain( production.Descendants(), static current => "PackageReference" == current.Name.LocalName @@ -73,7 +73,7 @@ public void PublishedIdentityPreservesStableTerminalBoundary() { static current => "PackageReference" == current.Name.LocalName && "Icod.Terminal" == current.Attribute( "Include" )?.Value ); - Assert.Equal( "1.29.0", testTerminal.Attribute( "Version" )?.Value ); + Assert.Equal( "1.29.1", testTerminal.Attribute( "Version" )?.Value ); } private static string GetSingleValue( diff --git a/tests/Icod.DCurses.Tests/src/PublicTwoFourPackageGateTests.cs b/tests/Icod.DCurses.Tests/src/PublicTwoFourPackageGateTests.cs index 0b7a93093..f75ad9bf2 100644 --- a/tests/Icod.DCurses.Tests/src/PublicTwoFourPackageGateTests.cs +++ b/tests/Icod.DCurses.Tests/src/PublicTwoFourPackageGateTests.cs @@ -25,7 +25,7 @@ namespace Icod.DCurses.Tests; using System.Xml.Linq; using Xunit; -/// Guards the durable 2.4 package documentation and consumer witnesses. +/// Guards durable package documentation and consumer witnesses. public sealed partial class PublicTwoFourPackageGateTests { [Fact] public void PackageReadmeIsDurableAndUsesResolvableLinks() { @@ -63,7 +63,7 @@ public void SampleGuidesDescribeTheCurrentReleaseAndEditorFoundation() { ) ); - Assert.Contains( "Icod.Terminal 1.29.0", readme, StringComparison.Ordinal ); + Assert.Contains( "Icod.Terminal 1.29.1", readme, StringComparison.Ordinal ); Assert.Contains( "clipboard", readme, StringComparison.OrdinalIgnoreCase ); Assert.Contains( "cursor style", readme, StringComparison.OrdinalIgnoreCase ); Assert.Contains( "pointer capture", readme, StringComparison.OrdinalIgnoreCase ); @@ -72,7 +72,7 @@ public void SampleGuidesDescribeTheCurrentReleaseAndEditorFoundation() { StringComparison.OrdinalIgnoreCase ); Assert.DoesNotContain( "Icod.Terminal 1.28.0", readme, StringComparison.Ordinal ); - Assert.Contains( "current 2.4.0 source consumes stable `Icod.Terminal 1.29.0`", + Assert.Contains( "current 2.5.1 source consumes stable `Icod.Terminal 1.29.1`", rasterReadme, StringComparison.Ordinal ); Assert.DoesNotContain( "stable 2.3.1 maintenance source", rasterReadme, StringComparison.OrdinalIgnoreCase ); @@ -86,7 +86,7 @@ public void SampleGuidesDescribeTheCurrentReleaseAndEditorFoundation() { } [Fact] - public void PackageConsumerAndVerifierFreezeTheTwoFourBoundary() { + public void PackageConsumerAndVerifierPreserveTheCurrentBoundary() { string root = FindRepositoryRoot(); string smoke = File.ReadAllText( Path.Combine( root, "tools", "package-smoke", "Program.cs" ) @@ -100,11 +100,11 @@ public void PackageConsumerAndVerifierFreezeTheTwoFourBoundary() { Assert.Contains( "WriteClipboardAsync", smoke, StringComparison.Ordinal ); Assert.Contains( "ReadClipboardAsync", smoke, StringComparison.Ordinal ); Assert.Contains( "AcquireCursorStyleAsync", smoke, StringComparison.Ordinal ); - Assert.Contains( "TerminalPackageVersion = \"1.29.0\"", verifier, + Assert.Contains( "TerminalPackageVersion = \"1.29.1\"", verifier, StringComparison.Ordinal ); - Assert.Contains( "clipboard", GetSingleValue( project, "PackageReleaseNotes" ), + Assert.Contains( "Ctrl+C", GetSingleValue( project, "PackageReleaseNotes" ), StringComparison.OrdinalIgnoreCase ); - Assert.Contains( "cursor", GetSingleValue( project, "PackageReleaseNotes" ), + Assert.Contains( "Ctrl+Z", GetSingleValue( project, "PackageReleaseNotes" ), StringComparison.OrdinalIgnoreCase ); } diff --git a/tools/package-smoke/README.md b/tools/package-smoke/README.md index 329ed50a2..b4db0e176 100644 --- a/tools/package-smoke/README.md +++ b/tools/package-smoke/README.md @@ -2,7 +2,7 @@ This project is intentionally not part of `Icod.DCurses.sln` and has no project reference to the repository library. -Package validation copies the smoke project and its explicitly staged source witnesses into a temporary directory, uses an isolated NuGet package cache, restores the exact current DCurses package version from the local artifact directory, and resolves the package-declared dependency graph. The 2.4 package declares `Icod.Terminal 1.29.0` directly; `Icod.TermInfo` remains a transitive dependency through Terminal. +Package validation copies the smoke project and its explicitly staged source witnesses into a temporary directory, uses an isolated NuGet package cache, restores the exact current DCurses package version from the local artifact directory, and resolves the package-declared dependency graph. The 2.5.1 package declares `Icod.Terminal 1.29.1` directly; `Icod.TermInfo` remains a transitive dependency through Terminal. Dependency versions are not duplicated as verifier policy. The package metadata is authoritative; restore/build/run establish whether the generated package is consumable with its declared dependency graph. diff --git a/tools/package-verifier/Program.cs b/tools/package-verifier/Program.cs index ff5150cdb..d07c8fc6d 100644 --- a/tools/package-verifier/Program.cs +++ b/tools/package-verifier/Program.cs @@ -14,7 +14,7 @@ internal static class Program { private const string PackageCopyright = "Copyright (c) 2026 Timothy J. Bruce"; private const string RepositoryUrl = "https://github.com/uniblab/Icod.DCurses"; private const string TerminalPackageId = "Icod.Terminal"; - private const string TerminalPackageVersion = "1.29.0"; + private const string TerminalPackageVersion = "1.29.1"; private static readonly string[] TargetFrameworks = [ "net8.0", "net9.0",