Skip to content

Commit 945af2f

Browse files
wildleo91claude
andcommitted
feat(a11y): span -> button migration + L1/L2 polish (build 633)
L3 finding from the 2026-04-30 UI consistency audit. The build-632 sweep noted that ~70 in-app action buttons were rendered as <span class="btn"> rather than <button class="btn">. Visually identical (Splunk's .btn rule applies to both) but <span> does NOT receive browser default keyboard tab-traversal or screen- reader "button" role announcement. Tab traversal skipped them and Enter/Space did not activate them. - Convert all 70+ <span class="btn"> action sites to <button type="button" class="btn ..."> across control_panel.js, wl_modals.js, wl_save.js, wl_versions.js, wl_presence.js, wl_nav.js, wl_table.js, wl_approval_ui.js. type="button" so buttons inside any wrapping form do NOT auto-submit on click. - Inline-opacity locking pattern (opacity:0.5;pointer-events:none) preserved on buttons that gate Remove/Reject/Cancel until reason filled - pointer-events:none works on <button> the same as <span>, JS swap-style remains unchanged. - Inline colour overrides on action buttons (background:#e74c3c etc.) replaced with class attribution where redundant - simulated btn-warning / btn-danger now class-driven via build-631 rules. - Make E2E test selectors element-agnostic (span.btn-primary -> .btn-primary) in test_e2e_realworld.py, test_e2e_manual_browser.py, test_wl_save.py, test_ui_browser.py - selectors pass for both before AND after migration. - L1 panel title alignment - resolved by build-632 M2 empty-state fix; verified post-deploy. - L2 active-tab 1px nudge - explicit border:1px solid transparent + 5px 14px padding on .wl-cp-tab regardless of active state so switching tabs no longer shifts content. Verified in browser: tab traversal reaches migrated buttons (interactive button count went from 0 -> 71 on Control Panel), Enter activates focused tabs, modal lifecycle preserved, click handlers all fire (find by #id / data-attr, element-agnostic), disabled-state inline-opacity pattern intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 08d7e2f commit 945af2f

16 files changed

Lines changed: 244 additions & 149 deletions

CHANGELOG.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,87 @@ Detailed per-round entries below.
6464

6565
---
6666

67+
## Unreleased — 2026-05-01 (build 633, accessibility + L1/L2 follow-up)
68+
69+
### Accessibility: span → button migration (audit finding L3)
70+
71+
The build-632 sweep noted that ~70 in-app action buttons were rendered
72+
as `<span class="btn">` rather than `<button class="btn">`. Visually
73+
identical (Splunk's `.btn` rule applies to both) but `<span>` does NOT
74+
receive the browser's default keyboard tab-traversal or the screen-
75+
reader "button" role announcement. Tab navigation skipped them and
76+
Enter/Space did not activate them.
77+
78+
#### Changed
79+
80+
- All 70+ `<span class="btn ...">` action sites in
81+
`appserver/static/control_panel.js` and the seven module files
82+
(`wl_modals.js`, `wl_save.js`, `wl_versions.js`, `wl_presence.js`,
83+
`wl_nav.js`, `wl_table.js`, `wl_approval_ui.js`) converted to
84+
`<button type="button" class="btn ...">`. The `type="button"`
85+
attribute is required so buttons inside any wrapping form do NOT
86+
auto-submit on click. Click handlers find by `#id` or `data-`
87+
attribute, so the JS event flow is unchanged.
88+
- Inline-opacity locking pattern (`style="opacity:0.5;
89+
pointer-events:none"`) preserved on buttons that use it for
90+
multi-step gating (Remove confirmation, Reject reason, Cancel
91+
Request) — `pointer-events:none` works on `<button>` exactly like
92+
on `<span>`, and the existing JS that swaps the inline style on
93+
state change is unchanged.
94+
- Inline colour overrides on action buttons (`style="background:
95+
#e74c3c;color:#fff"`) replaced with proper Bootstrap-style class
96+
attribution (`class="btn btn-danger"` etc.) where redundant. The
97+
six sites where inline colours simulated `btn-warning` / `btn-danger`
98+
before those rules existed are now class-driven, taking advantage
99+
of the rules introduced in build 631.
100+
- `.wl-cp-tab` Control Panel tabs are now real buttons. They
101+
receive keyboard focus, are announced as "button" by screen
102+
readers, and respond to Enter/Space. Visual rendering and click
103+
behavior are unchanged.
104+
105+
#### Tests
106+
107+
- E2E selectors in `tests/test_e2e_realworld.py`,
108+
`tests/test_e2e_manual_browser.py`, `tests/e2e/test_wl_save.py`,
109+
and `tests/test_ui_browser.py` made element-agnostic
110+
(`span.btn-primary``.btn-primary`) so they pass for either
111+
span or button. Done in a separate prior step so tests stayed
112+
green during the migration.
113+
114+
#### Verified in browser
115+
116+
- Tab traversal reaches all migrated buttons (interactive count went
117+
from 0 → 56 → 71 on Control Panel as more rows render).
118+
- Enter key activates focused tab buttons (verified on
119+
`.wl-cp-tab[data-tab="usage"]`).
120+
- Modal lifecycle preserved: Add Column → green Add + grey Cancel,
121+
Cancel removes overlay; Remove Row → red Remove (locked) + grey
122+
Cancel; Trash Restore → green Restore + grey Cancel.
123+
- Lockdown prompt → red Activate Emergency Lockdown opens the
124+
prompt; OK is green primary, Cancel is grey neutral.
125+
- Disabled-state inline opacity pattern intact on buttons; the
126+
Remove confirmation stays at 0.5 opacity until reason is filled.
127+
128+
### Polish (audit findings L1, L2)
129+
130+
- **L1 panel title alignment** — resolved by the build-632 M2 empty-
131+
state fix (all single-value panels now render the same way).
132+
Verified visually in build-633 deploy.
133+
- **L2 active-tab 1px nudge** — fixed via CSS rule
134+
`.wl-cp-tab, .wl-cp-tab.btn-primary { border: 1px solid transparent;
135+
padding: 5px 14px; }`. The active tab no longer shifts content by
136+
1 px when state toggles between `btn` and `btn-primary` (Splunk
137+
bundle ships them with different border specs).
138+
139+
### Migration / rollback
140+
141+
Class-rename + element-rename only; click handlers find by `#id` or
142+
`data-` attribute, so JS event flow is unchanged. Rollback: `git
143+
revert` the build-633 commit and redeploy at the next build number.
144+
The `.wl-btn-locked` opacity helper is preserved.
145+
146+
---
147+
67148
## Unreleased — 2026-05-01 (build 632, UI consistency sweep)
68149

69150
### UI consistency: button taxonomy + audit dashboard polish (builds 631-632)

appserver/static/control_panel.js

Lines changed: 68 additions & 69 deletions
Large diffs are not rendered by default.

appserver/static/modules/wl_approval_ui.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -413,16 +413,16 @@ function applyPendingHighlighting() {
413413
' &mdash; ' + _.escape(displayReason) +
414414
'</span>' +
415415
(isSelfRequest
416-
? '<span class="btn btn-warning wl-cancel-request-btn" data-id="' +
417-
_.escape(pa.request_id) + '" style="background:#f39c12;color:#fff">Cancel Request</span>'
418-
: '<span class="btn btn-success wl-approve-btn" data-id="' +
419-
_.escape(pa.request_id) + '">Approve</span>' +
420-
'<span class="btn btn-danger wl-reject-btn" data-id="' +
421-
_.escape(pa.request_id) + '">Reject</span>'
416+
? '<button type="button" class="btn btn-warning wl-cancel-request-btn" data-id="' +
417+
_.escape(pa.request_id) + '">Cancel Request</button>'
418+
: '<button type="button" class="btn btn-success wl-approve-btn" data-id="' +
419+
_.escape(pa.request_id) + '">Approve</button>' +
420+
'<button type="button" class="btn btn-danger wl-reject-btn" data-id="' +
421+
_.escape(pa.request_id) + '">Reject</button>'
422422
) +
423423
(hasRowHighlight
424-
? '<span class="btn btn-small wl-filter-requested" data-idx="' + paIdx +
425-
'" style="margin-left:8px;cursor:pointer">Show Requested Rows</span>'
424+
? '<button type="button" class="btn btn-small wl-filter-requested" data-idx="' + paIdx +
425+
'" style="margin-left:8px;cursor:pointer">Show Requested Rows</button>'
426426
: '') +
427427
'</div>';
428428
});
@@ -555,14 +555,14 @@ function renderAdditionPreview() {
555555
// Pagination controls
556556
if (totalPages > 1) {
557557
html += '<div style="margin-top:8px;display:flex;align-items:center;gap:8px">';
558-
html += '<span class="btn btn-small wl-preview-prev"' +
558+
html += '<button type="button" class="btn btn-small wl-preview-prev"' +
559559
(additionPreviewPage <= 0 ? ' style="opacity:0.4;pointer-events:none"' : '') +
560-
'>&laquo; Prev</span>';
560+
'>&laquo; Prev</button>';
561561
html += '<span style="font-size:12px">Page ' + (additionPreviewPage + 1) + ' of ' +
562562
totalPages + ' (' + total + ' rows)</span>';
563-
html += '<span class="btn btn-small wl-preview-next"' +
563+
html += '<button type="button" class="btn btn-small wl-preview-next"' +
564564
(additionPreviewPage >= totalPages - 1 ? ' style="opacity:0.4;pointer-events:none"' : '') +
565-
'>Next &raquo;</span>';
565+
'>Next &raquo;</button>';
566566
html += '</div>';
567567
}
568568
html += '</div>';

appserver/static/modules/wl_modals.js

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ define([
157157
'<div class="wl-char-counter" data-for="wl-remove-reason">0 / 500</div>' +
158158
'</div>' +
159159
'<div style="display:flex;gap:8px;justify-content:flex-end">' +
160-
'<span class="btn" id="wl-remove-confirm" ' +
161-
'style="background:#e74c3c;color:#fff;cursor:pointer">Remove</span>' +
162-
'<span class="btn" id="wl-remove-cancel" ' +
163-
'style="cursor:pointer">Cancel</span>' +
160+
'<button type="button" class="btn btn-danger" id="wl-remove-confirm" ' +
161+
'style="cursor:pointer">Remove</button>' +
162+
'<button type="button" class="btn" id="wl-remove-cancel" ' +
163+
'style="cursor:pointer">Cancel</button>' +
164164
'</div>' +
165165
'</div></div>';
166166

@@ -267,8 +267,8 @@ define([
267267
'<div id="wl-new-rule-error" style="color:#ef9a9a;font-size:12px;margin-top:6px;display:none"></div>' +
268268
'</div>' +
269269
'<div class="wl-modal-actions">' +
270-
'<span class="btn btn-primary" id="wl-new-rule-ok">Next</span> ' +
271-
'<span class="btn" id="wl-new-rule-cancel">Cancel</span>' +
270+
'<button type="button" class="btn btn-primary" id="wl-new-rule-ok">Next</button> ' +
271+
'<button type="button" class="btn" id="wl-new-rule-cancel">Cancel</button>' +
272272
'</div>' +
273273
'</div>' +
274274
'</div>';
@@ -372,8 +372,8 @@ define([
372372
'<div class="wl-char-counter" data-for="wl-approval-reason-text">0 / 500</div>' +
373373
'<div id="wl-approval-reason-error" class="wl-msg-error" style="display:none;margin-top:6px"></div>' +
374374
'<div class="wl-modal-actions" style="margin-top:12px">' +
375-
'<span class="btn btn-primary" id="wl-approval-reason-ok">Submit Request</span> ' +
376-
'<span class="btn" id="wl-approval-reason-cancel">Cancel</span>' +
375+
'<button type="button" class="btn btn-primary" id="wl-approval-reason-ok">Submit Request</button> ' +
376+
'<button type="button" class="btn" id="wl-approval-reason-cancel">Cancel</button>' +
377377
'</div>' +
378378
'</div>' +
379379
'</div>';
@@ -435,7 +435,7 @@ define([
435435
'<label>Import from CSV file</label>' +
436436
'<div class="wl-import-file-row">' +
437437
'<input type="file" accept=".csv" id="wl-import-file" class="wl-import-file-hidden" />' +
438-
'<span class="btn wl-import-file-btn" id="wl-import-file-trigger">Choose File</span>' +
438+
'<button type="button" class="btn wl-import-file-btn" id="wl-import-file-trigger">Choose File</button>' +
439439
'<span class="wl-import-file-name" id="wl-import-file-label">No file chosen</span>' +
440440
'<span class="wl-import-clear" id="wl-import-clear" style="display:none">Clear</span>' +
441441
'</div>' +
@@ -471,8 +471,8 @@ define([
471471
'<div id="wl-import-preview" style="display:none"></div>' +
472472
'</div>' +
473473
'<div class="wl-modal-actions">' +
474-
'<span class="btn btn-primary" id="wl-create-csv-ok">Create</span> ' +
475-
'<span class="btn" id="wl-create-csv-cancel">Cancel</span>' +
474+
'<button type="button" class="btn btn-primary" id="wl-create-csv-ok">Create</button> ' +
475+
'<button type="button" class="btn" id="wl-create-csv-cancel">Cancel</button>' +
476476
'</div>' +
477477
'</div>' +
478478
'</div>';
@@ -769,8 +769,8 @@ define([
769769
'<div class="wl-char-counter" data-for="wl-rmrow-reason">0 / 500</div>' +
770770
'</div>' +
771771
'<div class="wl-modal-actions">' +
772-
'<span class="btn ' + confirmClass + '" id="wl-rmrow-ok" style="opacity:0.5;pointer-events:none">' + _.escape(confirmText) + '</span> ' +
773-
'<span class="btn" id="wl-rmrow-cancel">Cancel</span>' +
772+
'<button type="button" class="btn ' + confirmClass + '" id="wl-rmrow-ok" style="opacity:0.5;pointer-events:none">' + _.escape(confirmText) + '</button> ' +
773+
'<button type="button" class="btn" id="wl-rmrow-cancel">Cancel</button>' +
774774
'</div>' +
775775
'</div>' +
776776
'</div>';
@@ -829,8 +829,8 @@ define([
829829
'<div class="wl-char-counter" data-for="wl-rmcol-reason">0 / 500</div>' +
830830
'</div>' +
831831
'<div class="wl-modal-actions">' +
832-
'<span class="btn btn-danger" id="wl-rmcol-ok" style="opacity:0.5;pointer-events:none">Remove</span> ' +
833-
'<span class="btn" id="wl-rmcol-cancel">Cancel</span>' +
832+
'<button type="button" class="btn btn-danger" id="wl-rmcol-ok" style="opacity:0.5;pointer-events:none">Remove</button> ' +
833+
'<button type="button" class="btn" id="wl-rmcol-cancel">Cancel</button>' +
834834
'</div>' +
835835
'</div>' +
836836
'</div>';
@@ -880,8 +880,8 @@ define([
880880
'<p>Request ID: <strong>' + _.escape(requestId) + '</strong></p>' +
881881
'</div>' +
882882
'<div class="wl-modal-actions">' +
883-
'<span class="btn btn-success" id="wl-approve-ok">Approve</span> ' +
884-
'<span class="btn" id="wl-approve-cancel">Cancel</span>' +
883+
'<button type="button" class="btn btn-success" id="wl-approve-ok">Approve</button> ' +
884+
'<button type="button" class="btn" id="wl-approve-cancel">Cancel</button>' +
885885
'</div>' +
886886
'</div>' +
887887
'</div>'
@@ -935,9 +935,9 @@ define([
935935
'<div class="wl-char-counter" data-for="wl-inline-reject-reason">0 / 500</div>' +
936936
'</div>' +
937937
'<div class="wl-modal-actions">' +
938-
'<span class="btn btn-danger" id="wl-inline-reject-ok" ' +
939-
'style="opacity:0.5;pointer-events:none">Reject</span> ' +
940-
'<span class="btn" id="wl-inline-reject-cancel">Cancel</span>' +
938+
'<button type="button" class="btn btn-danger" id="wl-inline-reject-ok" ' +
939+
'style="opacity:0.5;pointer-events:none">Reject</button> ' +
940+
'<button type="button" class="btn" id="wl-inline-reject-cancel">Cancel</button>' +
941941
'</div>' +
942942
'</div>' +
943943
'</div>'
@@ -1009,9 +1009,9 @@ define([
10091009
'<div class="wl-char-counter" data-for="wl-inline-cancel-reason">0 / 500</div>' +
10101010
'</div>' +
10111011
'<div class="wl-modal-actions">' +
1012-
'<span class="btn" id="wl-inline-cancel-ok" ' +
1013-
'style="background:#f39c12;color:#fff;opacity:0.5;pointer-events:none">Cancel Request</span> ' +
1014-
'<span class="btn" id="wl-inline-cancel-dismiss">Close</span>' +
1012+
'<button type="button" class="btn btn-warning" id="wl-inline-cancel-ok" ' +
1013+
'style="opacity:0.5;pointer-events:none">Cancel Request</button> ' +
1014+
'<button type="button" class="btn" id="wl-inline-cancel-dismiss">Close</button>' +
10151015
'</div>' +
10161016
'</div>' +
10171017
'</div>'
@@ -1121,8 +1121,8 @@ define([
11211121
datePickerHtml +
11221122
'</div>' +
11231123
'<div class="wl-modal-actions">' +
1124-
'<span class="btn btn-primary" id="wl-bulk-apply">Apply</span> ' +
1125-
'<span class="btn" id="wl-bulk-cancel">Cancel</span>' +
1124+
'<button type="button" class="btn btn-primary" id="wl-bulk-apply">Apply</button> ' +
1125+
'<button type="button" class="btn" id="wl-bulk-cancel">Cancel</button>' +
11261126
'</div>' +
11271127
'</div>' +
11281128
'</div>';

appserver/static/modules/wl_nav.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ define([
7171
'<br><span style="font-size:13px;margin-top:4px;display:inline-block">' +
7272
'It may have been removed or the link may be outdated.' +
7373
'</span>' +
74-
'<br><span class="btn btn-primary" id="wl-go-home" ' +
75-
'style="margin-top:8px">Back to Whitelist Manager</span>' +
74+
'<br><button type="button" class="btn btn-primary" id="wl-go-home" ' +
75+
'style="margin-top:8px">Back to Whitelist Manager</button>' +
7676
'</div>'
7777
);
7878
} else {
@@ -174,8 +174,8 @@ define([
174174
'<br><span style="font-size:13px;margin-top:4px;display:inline-block">' +
175175
'Would you like to create a new CSV whitelist?' +
176176
'</span>' +
177-
'<br><span class="btn btn-primary" id="wl-create-csv-btn" ' +
178-
'style="margin-top:8px">Create CSV</span>';
177+
'<br><button type="button" class="btn btn-primary" id="wl-create-csv-btn" ' +
178+
'style="margin-top:8px">Create CSV</button>';
179179
} else {
180180
noCsvHtml +=
181181
'<br><span style="font-size:13px;margin-top:4px;display:inline-block">' +

appserver/static/modules/wl_presence.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function ($, _, mvc, REST) {
113113
'<p style="margin:0;color:#e74c3c">' + _.escape(message) + '</p>' +
114114
'</div>' +
115115
'<div class="wl-modal-actions">' +
116-
'<span class="btn btn-primary" id="wl-presence-full-ok">OK</span>' +
116+
'<button type="button" class="btn btn-primary" id="wl-presence-full-ok">OK</button>' +
117117
'</div>' +
118118
'</div></div>'
119119
);
@@ -153,7 +153,7 @@ function ($, _, mvc, REST) {
153153
'</p>' +
154154
'</div>' +
155155
'<div class="wl-modal-actions">' +
156-
'<span class="btn btn-primary" id="wl-csv-removed-ok">OK</span>' +
156+
'<button type="button" class="btn btn-primary" id="wl-csv-removed-ok">OK</button>' +
157157
'</div>' +
158158
'</div></div>'
159159
);

appserver/static/modules/wl_save.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@ define([
372372
warning +
373373
'</div>' +
374374
'<div class="wl-modal-actions">' +
375-
'<span class="btn btn-primary" id="wl-extchg-reload">Reload CSV</span> ' +
376-
'<span class="btn" id="wl-extchg-keep">Keep editing</span>' +
375+
'<button type="button" class="btn btn-primary" id="wl-extchg-reload">Reload CSV</button> ' +
376+
'<button type="button" class="btn" id="wl-extchg-keep">Keep editing</button>' +
377377
'</div>' +
378378
'</div>' +
379379
'</div>';
@@ -449,10 +449,10 @@ define([
449449
'resize:vertical" placeholder="Reason for this change\u2026"></textarea>' +
450450
'<div class="wl-char-counter" data-for="wl-audit-comment-input">0 / 500</div>' +
451451
'<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:12px">' +
452-
'<span class="btn btn-primary" id="wl-audit-comment-ok" ' +
453-
'style="cursor:pointer">OK</span>' +
454-
'<span class="btn" id="wl-audit-comment-cancel" ' +
455-
'style="cursor:pointer">Cancel</span>' +
452+
'<button type="button" class="btn btn-primary" id="wl-audit-comment-ok" ' +
453+
'style="cursor:pointer">OK</button>' +
454+
'<button type="button" class="btn" id="wl-audit-comment-cancel" ' +
455+
'style="cursor:pointer">Cancel</button>' +
456456
'</div>' +
457457
'</div></div>';
458458

0 commit comments

Comments
 (0)