Name dialogs and contain native overlay keyboard focus

This commit is contained in:
Joseph Doherty
2026-11-13 17:42:14 -05:00
parent cdd3adf6fc
commit 87c882ec0a
46 changed files with 1353 additions and 119 deletions
@@ -0,0 +1,41 @@
# ScadaBridge UI consistency — first batch review
Baseline accepted G1 pilot commit: `cdd3adf6`; Theme 1.0.0 published exact package. This batch follows parent G1 acceptance; it does not change package pins, policies, validators, UTC handling, lazy loading or fleet services. Secrets.Ui adoption awaits SEC07 release. No deploys or live writes.
## Scope grouped for review
`evidence/sb01-03/source-groups.json` lists every changed path; `source-freeze-sha256.json` records the frozen review source. No SB04+ work started.
- **Dialog naming/lifecycle:** DialogHost title/body/prompt ID relationships; AuditDrilldownDrawer modal/name/Escape; native `dialog.js` per-overlay Map state, actual-event synchronous Tab prevention, filtered focusable children, reference-counted inert siblings, scroll-lock ownership, detached-node cleanup, connected-trigger or heading fallback; component disposal invokes deactivation. Existing Confirm/Prompt/Show result semantics and drawer OnClose are preserved. Generic extraction waits for a second consumer.
- **Label/ID associations:** 126 previously standalone labels associated with native controls across28 files, IDs scoped to component instances; repeated alarm-band keys include the band key. Mutually exclusive numeric/text/Boolean match controls share one relationship safely. Audit multi-select names combine visible field label and current summary; shared table search has a visible label and unique ID. Remaining contextual searches/table cells/move controls receive explicit names without changing bindings/callbacks/conditions.
- **Intentional caption conversions:** `caption-conversions.json` enumerates files. Composite schema/editor sections, result payloads, console output, button-group captions and list headings use caption divs instead of labels targeting no labelable element. Existing layout classes and content are retained; no output or parameter behavior changed.
- **Validation relationships:** OPC/Mx existing FieldError selection logic is retained and factored into a helper. Only fields already mapped by RenderFieldError get conditional aria-invalid, describedby and an error ID/alert. Successful removal of Errors removes invalidity and stale description. No new validation rule or validation bypass.
- **Tests:** existing dialog/drawer/OPC suites extended; new ControlLabelTests checks table search after typing. Strict clipboard test explicitly configures the new lifecycle interop and retains its clipboard payload assertions; no test mode weakening/skips.
## Test evidence
`red-dialogs.log`: two intended missing-name/modal failures. `red-labels.log` is retained as an invalid initial test fixture (required RowContent omitted); corrected fixture `red-labels-corrected-fixture.log` fails for the intended missing input ID. `red-validation.log` retains a compile error from a duplicate attribute found during the broad scan, then corrected; `red-validation-corrected.log` shows the intended missing aria-invalid failure. Duplicate ParkedMessages attribute was removed; its existing contextual accessible label remains unchanged.
Focused related suite: `green-batch.log`104 passed. Intermediate full suite: `full-suite.log`1554 passed/zero skipped before the last validation regression and source naming completion. Final frozen-source suite passed **1555/1555**, zero failures/skips (`final-suite.log`, `sb01-03-final.trx`).
## Browser pilot and remaining gates
Disposable new fixture source is retained in umbrella `components/ui-theme/validation/scadabridge-ui/host`; actual source RCL ProjectReference, anonymous/synthetic local services only. It binds127.0.0.1:5201, separate from retained G1 canary5199. Existing rigs untouched. `infra/tools/ui_consistency_browser.py` accepts explicit loopback base URL/storage-state/output directory; no real writes.
First matrix and one confirmation are retained separately under `browser/` and `browser-confirmation/`. Both passed name/prompt linkage, synchronous first/last Tab and Shift+Tab, hidden/disabled filtering, inert background, Escape, normal trigger focus return, lock cleanup, repeat opening and no-focusable containment at1440/390. **Disconnected-trigger fallback failed**: the native code focused the heading, then immediately removing its temporary tabindex made Chromium blur to BODY. Source now restores that attribute only after blur. No third broad screenshot/polish loop is claimed; A focused no-screenshot technical probe (`focused-fallback.json`) now confirms all three actual hosts restore the disconnected trigger to h1, retain tabindex while focused, clear it on blur, and release locks/inert. Parent lifecycle review remains required.
SB03 source inventory does not prove every composite editor's internal accessible name or field help/error relationship at runtime. Remaining work before calling SB03 complete: machine-check frozen associations, contextual duplicate IDs on repeated editors, all existing form help relationships, Monaco composite context, and rendered browser/assistive-tree samples for transport/notification/instance forms. Aggregate form errors must stay aggregate rather than falsely marking all fields invalid. These are explicit pending acceptance items, not covered by passing unit tests.
## Lifecycle review refinement
The supported policy is **one active overlay**, including the audit drawer and global DialogHost. No arbitrary nested or sibling overlay support is implied by the internal Map/ref-count bookkeeping. Native activation rejects any different active overlay or inert target. C# cancels a rejected service dialog through its unchanged typed cancellation path, or invokes the drawer's existing OnClose callback. DialogService's original second-dialog guard is retained. This prevents descendant Escape bubbling from closing two simultaneously active hosts.
A disposed DialogHost unsubscribes, cancels any pending local result, and deactivates its presentation. No new busy-operation semantics are introduced: confirm merely resolves the caller's result; domain work remains owned by the caller after the dialog closes. The adapter does not cancel or retry domain work. Source scan found no remaining callers of removed captureActiveElement/restoreActiveElement/focusTrap globals in src/tests. Browser evidence for the single-active rejection is `single-active-overlay.json`; it programmatically attempts a second local presentation while the real drawer is active, verifies cancellation/one remaining drawer, then verifies one Escape closes it and restores its trigger.
Final lifecycle-refined full suite: **1556/1556 passed**, zero skips/failures, `reviewed-suite.log` / `sb01-03-reviewed.trx`. This supersedes1555 for the additional rejection/disposal regression. No first-batch commit until parent source/lifecycle review accepts the frozen diff. Rollback reverts this batch only; accepted G1 package/palette pilot is separate.
## Peer finding closure and shared patch adoption
Native focus restoration now verifies document.activeElement after attempting the trigger; still-connected disabled/hidden/unfocusable triggers use heading fallback too. `unusable-trigger-red.json` preserves the reproduced disabled/hidden failures, `unusable-trigger-green.json` verifies all four target conditions and clean lock/inert/tabindex restoration. The retained Python test includes a span made unfocusable while still connected, not merely a replacement/disconnection.
Published Theme1.0.1 is restored from the official Gitea index into task-local `/tmp/scadabridge-theme101-packages`. Package SHA256995b117b0245577598c91404ed7957c071ebe3413696c8be48b419c9deb1bd5d and runtime DLL parity are in theme101-provenance.json; no stale default cache accepted. Full suite on the patch:1557/1557 passed, zero skips/failures. The added contextual TreeView checkbox regression remains SB03 work; its red failure is retained.