DECISION: a pager whose only two controls are permanently disabled is noise, so
the conventional behaviour ships — the bar is hidden when the result set is a
single page. NotificationReport, which raised the finding, opts in.
RATIONALE for opt-in rather than default-on. OffsetPager is shared, and its
summary span ("Page N of Y · T total") doubles as the ONLY total-count readout
on ConfigurationAuditLog — auto-hiding there would silently delete the result
count for every query returning under a page, and the existing Playwright
fixture asserts "3 total" on exactly such a query. So the behaviour lives in the
shared component (reusable for the next consumer) behind HideWhenSinglePage,
default false, which keeps every current consumer byte-identical.
The guard fires only when single-page-ness is POSITIVELY established:
PageCount <= 1 AND Page <= 1 AND !HasNextPage. A null TotalCount means the host
cannot count, so the bar renders — the controls never vanish while a further
page still exists.
NotificationReport's own Playwright pagination test seeds 51 rows (2 pages) and
is unaffected. 5 new bUnit tests cover hide-on-single-page, hide-on-empty,
show-on-multi-page, show-when-TotalCount-unknown, and the default-off path.
TreeView<TItem> handled only Enter/Space on the chevron; the tree itself was
unreachable by keyboard. Implements the WAI-ARIA tree pattern:
- Roving tabindex on li[role=treeitem] — exactly one node is tabbable, so the
whole tree is a single Tab stop. Target resolved per render: last-focused node
while still visible -> SelectedKey -> first visible node. Browser focus follows
via a per-node ElementReference + FocusAsync in OnAfterRenderAsync, guarded by
the same JSException/JSDisconnectedException/InvalidOperationException triple
the context-menu focus already used (a no-op under bUnit).
- ArrowDown/ArrowUp move between VISIBLE nodes; ArrowRight expands a collapsed
branch else moves to first child; ArrowLeft collapses an expanded branch else
moves to parent; Home/End jump to first/last; Enter/Space activate through the
SAME path a click takes (OnContentClick / OnCheckboxToggle) so selection
semantics never diverge between input modes.
- ARIA: aria-level, aria-posinset, aria-setsize added; aria-selected now renders
true/false on selectable+checkbox trees and stays absent on non-selectable ones.
- Event scoping: @onkeydown:stopPropagation on the li, chevron, checkbox and
content slot, so nested nodes do not double-handle and consumer controls inside
NodeContent keep their own key handling. Browser scroll-on-Space/Arrow is
suppressed by a minimal NATIVE inline onkeydown on the root ul, targeted at
treeitems only — Blazor's preventDefault directive is all-or-nothing per
element, so on the li it would trap Tab and cancel Enter/Space on consumer
buttons. No CSP is configured, so the inline handler runs.
BuildVisibleNodes() mirrors RenderNode's visibility rules and must stay in step.
Tests: 31 new bUnit tests in TreeViewKeyboardNavigationTests; the 47 existing
TreeView tests are unchanged and still green. Docs: new keyboard/a11y section in
docs/components/TreeView.md.
Three dead-code bugs: --trigger-kind was registered but never read or forwarded on the
alarm-update, script-add, and script-update paths. Introduced TriggerConfigJson.InjectAnalysisKind
helper that rewrites any raw --trigger-config JSON blob, writing "analysisKind":"Strict" when
the flag is strict (case-insensitive) and stripping the key for any other value. Wired the
helper into all three handlers alongside the existing alarm-add path (which already used
AlarmTriggerConfigJson.Build). Added 6 unit tests for the new helper in TemplateTriggerKindTests.
Also fixed a false-positive bUnit test (AlarmTriggerEditor_Expression_NoAnalysisKindInConfig_
SelectorDefaultsAdvisory) that passed because "Advisory" appeared anywhere in the HTML; now
asserts select.GetAttribute("value") == "Advisory". Added the missing equivalent test for
ScriptTriggerEditor (ScriptTriggerEditor_Expression_NoAnalysisKindInConfig_SelectorDefaultsAdvisory).
Surfaces the T28a backend "analysisKind" discriminator in both authoring
surfaces: an Advisory|Strict <select> (id="alarm-trigger-kind" /
"script-trigger-kind") added to the Expression fragment of
AlarmTriggerEditor and ScriptTriggerEditor, and a --trigger-kind option
on template alarm/script add+update in the CLI.
Key/value contract: "analysisKind":"Strict" when strict; key omitted for
Advisory — exactly as ValidationService.IsStrictAnalysis reads it.
Selector only shown for Expression triggers; non-Expression triggers do
not emit the key even if IsStrictAnalysisKind is set on the model.
Both projects build 0 warnings; 101 CentralUI Trigger tests + 33 CLI
Template tests pass.