Skip to content

fix(374): per-file #nullable enable remediation for UtilitiesCS/Dialogs + misc verify-only files#385

Merged
drmoisan merged 12 commits into
epic/utilitiescs-nullable-remediation-integrationfrom
feature/utilitiescs-nullable-dialogs-misc-374
Jul 19, 2026
Merged

fix(374): per-file #nullable enable remediation for UtilitiesCS/Dialogs + misc verify-only files#385
drmoisan merged 12 commits into
epic/utilitiescs-nullable-remediation-integrationfrom
feature/utilitiescs-nullable-dialogs-misc-374

Conversation

@drmoisan

Copy link
Copy Markdown
Owner

Suggested title

fix(374): per-file #nullable enable remediation for UtilitiesCS/Dialogs + misc verify-only files

Summary

  • Adds a per-file #nullable enable pragma to 14 files: the 12 hand-written files under
    UtilitiesCS/Dialogs/ (Batches A-E) plus 2 verify-only files (UtilitiesCS/WindowsAPI/ExtraDeclarations.cs,
    UtilitiesCS/Properties/AssemblyInfo.cs), bringing all 14 to zero CS86xx diagnostics under the pragma.
  • Annotation and null-safety only: nullable annotations (?), null-forgiving operators (!) applied
    only where justified, no behavior changes, no refactors, no API redesign.
  • No project- or solution-level <Nullable> element is introduced; UtilitiesCS.csproj remains
    unopted-in at the project level (per-file architecture only).
  • The 4 Designer-generated sibling files (DelegateButtonTemplate.Designer.cs,
    FolderNotFoundViewer.Designer.cs, InputBoxViewer.Designer.cs, MyBoxViewer.Designer.cs) are
    untouched and remain non-opted-in.
  • Part of the utilitiescs-nullable-remediation epic (Wave 1); depends on issue Feature: utilitiescs-nullable-extensions #363
    (utilitiescs-nullable-extensions, merged as PR fix(363): nullable-reference remediation of UtilitiesCS/Extensions via per-file #nullable enable #379) for the WinFormsExtensions.Clone<T>()
    contract consumed by the button-wrapper trio and MyBox.ButtonTemplate.
  • All 6 acceptance criteria (AC1-AC6) are checked off in spec.md and user-story.md, each backed
    by cited evidence artifacts.

Why

The CI nullable gate (repaired by PR #361 to use msbuild /t:Rebuild, a genuine recompile) cannot
be enforced against new code until the pre-existing nullable-reference-type debt is remediated file
by file under a per-file #nullable enable opt-in architecture, since the codebase has no
project-level nullable context. This PR is the Wave-1 child that remediates the
UtilitiesCS/Dialogs/ cluster plus the smallest defensible "misc" component named by the epic's
dialogs-misc label. The maintainer-mandated constraint is annotation/null-safety only: no
behavior changes, no refactors, no API redesign, and no project-wide <Nullable> element.

What Changed

Core remediation (UtilitiesCS/Dialogs/, 12 files, batched leaf-first):

  • Batch A (leaves): DelegateButtonTemplate.cs, FolderNotFoundViewer.cs, MyBoxViewer.cs,
    InputBoxViewer.csFolderNotFoundViewer.FolderAction annotated string?;
    MyBoxViewer._map annotated Dictionary<string, Delegate>? with justified ! at the two
    click handlers.
  • Batch B (button wrapper trio): ActionButton.cs, DelegateButton.cs, FunctionButton.cs
    _name/_button/delegate-typed fields annotated nullable consistently across the trio;
    FunctionButton<T>.Value annotated T?; delegate invocations use justified !.
  • Batch C (direct viewer consumers): InputBox.cs (ShowDialog return type string?, matching
    its already-documented "or null if cancelled" behavior), NotImplementedDialog.cs (pragma-only,
    no annotation edits required).
  • Batch D (MyBox core): MyBox.csFunctionButtonGroup<T>.Result and the corresponding
    ShowDialog<T> return type annotated T?, consistent with the Batch B FunctionButton<T>.Value
    decision and the consumed WinFormsExtensions.Clone<T>() contract.
  • Batch E (MyBox dependents): MyBoxModeless.cs (internal 5-arg showAction parameter annotated
    Action<MyBoxViewer>?, reflecting its own documented null-defaulting behavior),
    YesNoToAll.cs (pragma-only).

Misc verify-only batch:

  • UtilitiesCS/WindowsAPI/ExtraDeclarations.cs, UtilitiesCS/Properties/AssemblyInfo.cs — pragma
    added; both confirmed zero-CS86xx with no annotation edits needed.

Docs/evidence/tests:

  • No test source files were added or modified (annotation-only change; existing
    UtilitiesCS.Test/Dialogs/ suite is the regression harness).
  • docs/features/active/utilitiescs-nullable-dialogs-misc/{spec.md,user-story.md} — AC1-AC6
    checked off.
  • docs/features/active/utilitiescs-nullable-dialogs-misc/evidence/** — baseline, per-batch
    qa-gates/regression-testing, and final-QC evidence (CSharpier, analyzer build, pragma-gate
    rebuild, MSTest+coverage, Cobertura XML).
  • docs/features/active/utilitiescs-nullable-dialogs-misc/{policy-audit,code-review, feature-audit}.2026-07-19T12-20.md — feature-review artifacts, blocking_count=0.

Architecture / How It Fits Together

This is a lexical, per-file change: each opted-in file gets #nullable enable at the top of its
namespace block. Because nullable context in C# is file-scoped (not assembly-scoped when no
project-level <Nullable> element exists), the 14 opted-in files independently gain
compiler-enforced null-flow analysis while every other file in the assembly — including the 4
Designer-generated siblings — remains in the oblivious nullable context it was in before. No
wiring, DI, or entry-point changes are involved. The one cross-module dependency is
WinFormsExtensions.Clone<T>() where T : Control (from issue #363, already merged and verified
non-null on the base tip), which the button-wrapper trio and MyBox.ButtonTemplate's setter
consume; the button-wrapper .Button/.Name/.Delegate accessors are kept non-null (via
justified ! on nullable-in-storage backing fields) specifically to match that non-null contract.

Verification

Completed (from evidence artifacts, independently reproduced by feature-review, not merely
trusted from the executor's report):

  • CSharpier: clean, no residual formatting changes on final pass.
  • Analyzer build (/p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true): 0 errors.
  • Per-file nullable pragma gate (msbuild TaskMaster.sln /t:Rebuild ... /p:TreatWarningsAsErrors=true,
    deliberately without /p:Nullable=enable): 0 CS86xx across all 14 opted-in files. The
    solution-wide command short-circuits early on a pre-existing, out-of-scope vendored SVGControl
    CS0649 diagnostic; the feature-review reviewer independently re-ran an isolated
    UtilitiesCS-project-only rebuild as the authoritative zero-CS86xx detector and confirmed the
    result holds.
  • MSTest with coverage: 5702 passed / 0 failed, identical to the pre-change baseline (5702/0).
    Line coverage 83.82%, branch coverage 76.38% (post-change); changed-line coverage for the 14
    remediated files is 93.10% with no regression versus baseline (parsed independently from both
    Cobertura roots by the reviewer).
  • AC2: UtilitiesCS/UtilitiesCS.csproj has zero <Nullable> occurrences, confirmed before and
    after the change.
  • No nullable post-condition attributes ([NotNullWhen], [MaybeNullWhen], etc.) or polyfills
    were introduced (unavailable on this net481 target).
  • No banned APIs (DateTime.Now/UtcNow, Random.Shared, Thread.Sleep, Task.Delay) were
    introduced in the touched files.

Recommended (for reviewer re-verification, not run as part of this PR's evidence):

  • dotnet tool run csharpier .
  • msbuild TaskMaster.sln /t:Rebuild /p:Configuration=Debug /p:Platform="Any CPU" /p:TreatWarningsAsErrors=true
    (per-file pragma gate; do not add /p:Nullable=enable)
  • pwsh scripts/vscode/Invoke-MSTestWithCoverage.ps1

Backward Compatibility / Migration Notes

No breaking changes. Public signature changes are strictly additive nullability, each consistent
with already-documented behavior:

  • InputBox.ShowDialogstring? (already documented as returning null on cancel).
  • FunctionButton<T>.Value and MyBox.FunctionButtonGroup<T>.Result (and the corresponding
    ShowDialog<T> return type) → T? (unconstrained-generic contract decision, matching the
    helperclasses child's precedent for similar unconstrained-generic members).
  • MyBoxModeless's internal 5-arg showAction parameter → Action<MyBoxViewer>? (already
    documented null-defaulting behavior).
  • No project-level or solution-level <Nullable> element is added; non-opted-in files elsewhere
    in the repository are unaffected and not cross-blocked.

Risks and Mitigations

  • Risk: the per-file pragma gate deviates from the stock CLAUDE.md/.claude/rules/csharp.md
    type-check command (/p:Nullable=enable). Mitigation: this is a deliberate, epic-wide,
    documented architecture decision (see spec.md "Constraints & Risks"); using the global flag
    here would surface the whole epic's pre-existing nullable debt as false failures unrelated to
    this PR's scope. Not resolved by editing .claude/rules/*; deferred to the Wave-2 CI capstone
    child.
  • Risk: the solution-wide pragma-gate command aborts early on a pre-existing, out-of-scope
    vendored SVGControl CS0649 diagnostic. Mitigation: an isolated UtilitiesCS-project-only
    rebuild was used as the authoritative zero-CS86xx detector and independently reproduced by
    feature-review; the SVGControl issue is pre-existing and out of scope for this PR.
  • Risk: annotation-only changes to widely-used dialog helper types could mask a real null-flow
    bug if an annotation decision was wrong. Mitigation: every annotation decision documents the
    existing runtime behavior it reflects (e.g., already-documented null-on-cancel returns); the full
    regression suite (5702 tests) passes identically to baseline.

Review Guide

Suggested order:

  1. docs/features/active/utilitiescs-nullable-dialogs-misc/spec.md and user-story.md for the
    full acceptance-criteria mapping and constraints.
  2. UtilitiesCS/Dialogs/ActionButton.cs, DelegateButton.cs, FunctionButton.cs together (Batch
    B trio; the shared nullable-field-vs-guard decision is easiest to review as a set).
  3. UtilitiesCS/Dialogs/MyBox.cs (Batch D core) and its two dependents (MyBoxModeless.cs,
    YesNoToAll.cs).
  4. The remaining leaves and direct consumers (Batch A, C) and the 2 misc verify-only files.
  5. docs/features/active/utilitiescs-nullable-dialogs-misc/evidence/qa-gates/final-*.md for the
    final QC verification trail.

The large diff line count is dominated by generated Cobertura coverage XML fixtures under
docs/features/active/utilitiescs-nullable-dialogs-misc/evidence/** (baseline, per-batch, and
final coverage snapshots); the actual source diff is 14 files, each under ~25 changed lines.

Follow-ups

  • The helperclasses (Feature: utilitiescs-nullable-helperclasses #364) dependency-edge note in the epic manifest (depends_on: [extensions, helperclasses]) is flagged but not resolved by this PR: research found zero
    HelperClasses/ type references in UtilitiesCS/Dialogs/. Left for the epic-planner/maintainer.
  • The ~110-file ownership-gap table in spec.md (residual Interfaces/**, OutlookObjects/,
    EmailIntelligence/ residual, OneDriveHelpers/, Examples/, To Depricate/) is deliberately
    out of scope for this PR.
  • The rules-vs-convention conflict (.claude/rules/csharp.md documenting a global
    /p:Nullable=enable type-check step vs. this epic's per-file opt-in convention) is deferred to
    the Wave-2 CI capstone child.

GitHub Auto-close

drmoisan and others added 12 commits July 19, 2026 11:17
…tion

Record policy read receipt, file inventory (14 in-scope + 4 Designer-excluded),
upstream #363 Batch D precondition (satisfied), CSharpier/analyzer/nullable-gate
baselines, and MSTest+coverage baseline (5702 passed, line 83.80%, branch 76.35%).

Nullable pragma gate verified via scoped isolated UtilitiesCS build because the
plan-mandated solution-wide TWAE command aborts on pre-existing vendored SVGControl
CS0649 before compiling the cluster; zero CS86xx at baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add #nullable enable to DelegateButtonTemplate, FolderNotFoundViewer,
MyBoxViewer, InputBoxViewer. Annotate FolderNotFoundViewer.FolderAction as
string? (CS8618) and MyBoxViewer._map as nullable with justified ! in the click
handlers. DelegateButtonTemplate/InputBoxViewer are pragma-only. Zero CS86xx via
scoped isolated UtilitiesCS build; 5702 tests pass (no regression).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add #nullable enable to ActionButton, DelegateButton, FunctionButton. Annotate
the shared CS8618-prone fields (_name/_button/delegate-typed) nullable across the
trio; keep public Name/Button/Delegate signatures stable via runtime-neutral !
getters (AC5); FunctionButton<T>.Value -> T? per the unconstrained-generic
decision. Zero CS86xx via scoped isolated build; 5702 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add #nullable enable to InputBox and NotImplementedDialog. InputBox.ShowDialog
return type -> string? (documented null-on-cancel, CS8603); AsyncLocal invoker
seam unchanged. NotImplementedDialog pragma-only. Zero CS86xx; 5702 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add #nullable enable to MyBox. FunctionButtonGroup<T>.Result -> T? (CS8618) and
both ShowDialog<T> overloads -> T? to match (CS8603), the unconstrained-generic
decision consistent with FunctionButton<T>.Value. AsyncLocal invoker seam
unchanged; no new runtime guards. Zero CS86xx; 5702 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add #nullable enable to MyBoxModeless and YesNoToAll. MyBoxModeless 5-arg
showAction -> Action<MyBoxViewer>? (CS8625, invoked null from 4-arg overload);
[ExcludeFromCodeCoverage] and ?? fallback unchanged. YesNoToAll pragma-only
(AsyncLocal<enum> value type). Zero CS86xx; 5702 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Add #nullable enable to ExtraDeclarations.cs (entirely commented out) and
AssemblyInfo.cs (assembly attributes only). Pragma-only, no annotation edits;
zero CS86xx; 5702 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Full toolchain clean pass (CSharpier / analyzer build / scoped nullable gate /
MSTest+coverage). Zero CS86xx across all 14 remediated files; 5702 tests pass;
changed-line coverage regression NONE (cluster 93.10% baseline and final).
Verified AC2 (no <Nullable>), no post-condition attributes/polyfill, scope
guards (Designer untouched, <=500 lines, enums intact), AC5 signature compat,
AC6 no cross-block. Checked off AC1-AC6 in spec.md and user-story.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
Concurrent agents in different worktrees share global test tooling
(vstest/dotnet-coverage/testhost) and /tmp; document quiet-machine polling and
scratchpad-log discipline learned during #374 execution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXCu1xPtNbJs2HwKxgS9Bv
…ic-child #374

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eature-audit) - blocking_count=0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gs-misc-374 (sync with sibling children #370/#375)

# Conflicts:
#	.claude/agent-memory/atomic-executor/MEMORY.md
@drmoisan
drmoisan merged commit aa15479 into epic/utilitiescs-nullable-remediation-integration Jul 19, 2026
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