fix(voyage): move sidebar toggle outside aria-hidden region (09132940)
This commit is contained in:
parent
c08bde0649
commit
48ab3c9de3
2 changed files with 56 additions and 21 deletions
|
|
@ -540,9 +540,16 @@
|
||||||
}
|
}
|
||||||
.voyage-sidebar[aria-hidden="true"] .voyage-sidebar__panel { visibility: hidden; }
|
.voyage-sidebar[aria-hidden="true"] .voyage-sidebar__panel { visibility: hidden; }
|
||||||
|
|
||||||
/* 2-state FAB toggle (per critical decision #4) */
|
/* 2-state FAB toggle (per critical decision #4).
|
||||||
|
v4.3 Step 3 — position: fixed so toggle stays reachable when the
|
||||||
|
sidebar carries aria-hidden="true" (finding 09132940 a11y).
|
||||||
|
The toggle is now a sibling of the <aside>, not a descendant,
|
||||||
|
so the aria-hidden subtree no longer hides the toggle from AT. */
|
||||||
.voyage-fab {
|
.voyage-fab {
|
||||||
position: relative;
|
position: fixed;
|
||||||
|
top: var(--space-3);
|
||||||
|
right: 4px;
|
||||||
|
z-index: 901;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
@ -962,6 +969,28 @@
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- v4.3 Step 3 (finding 09132940) — toggle button is a sibling of the
|
||||||
|
<aside aria-hidden="true"> so it remains exposed to AT regardless
|
||||||
|
of sidebar-hidden state. CSS .voyage-fab uses position: fixed
|
||||||
|
(z-index: 901) so it floats over the sidebar at all times. -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="voyage-sidebar-toggle"
|
||||||
|
class="voyage-fab"
|
||||||
|
data-action="toggle-sidebar"
|
||||||
|
aria-controls="voyage-sidebar"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Skjul/vis annotation-panel"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
id="voyage-fab-badge"
|
||||||
|
class="voyage-fab__badge"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-label="Antall drafts ventende"
|
||||||
|
hidden
|
||||||
|
>0</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- v4.3 Step 19 — sidebar-rail (hidden-by-default) with ordered annotation-list,
|
<!-- v4.3 Step 19 — sidebar-rail (hidden-by-default) with ordered annotation-list,
|
||||||
filter (Alle/Åpne/Resolved), and jumplist count "X av N". Toggle via
|
filter (Alle/Åpne/Resolved), and jumplist count "X av N". Toggle via
|
||||||
FAB data-action="toggle-sidebar" or ] keyboard shortcut (Step 20). -->
|
FAB data-action="toggle-sidebar" or ] keyboard shortcut (Step 20). -->
|
||||||
|
|
@ -971,25 +1000,7 @@
|
||||||
aria-label="Annotation drafts sidebar"
|
aria-label="Annotation drafts sidebar"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<div class="voyage-sidebar__rail">
|
<div class="voyage-sidebar__rail" aria-hidden="true"></div>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
id="voyage-sidebar-toggle"
|
|
||||||
class="voyage-fab"
|
|
||||||
data-action="toggle-sidebar"
|
|
||||||
aria-controls="voyage-sidebar"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-label="Skjul/vis annotation-panel"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
id="voyage-fab-badge"
|
|
||||||
class="voyage-fab__badge"
|
|
||||||
aria-live="polite"
|
|
||||||
aria-label="Antall drafts ventende"
|
|
||||||
hidden
|
|
||||||
>0</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="voyage-sidebar__panel">
|
<div class="voyage-sidebar__panel">
|
||||||
<!-- v4.3 Step 19 — ordered annotation list with filter + jumplist count -->
|
<!-- v4.3 Step 19 — ordered annotation list with filter + jumplist count -->
|
||||||
<div class="voyage-annotation-list" aria-label="Ordered annotation list">
|
<div class="voyage-annotation-list" aria-label="Ordered annotation list">
|
||||||
|
|
|
||||||
|
|
@ -483,6 +483,30 @@ test('voyage-playground.html renderArtifact strips comments before md.render (v4
|
||||||
assert.ok(stripIdx > 0 && stripIdx < renderIdx, 'stripUnsafeComments must run before md.render');
|
assert.ok(stripIdx > 0 && stripIdx < renderIdx, 'stripUnsafeComments must run before md.render');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// v4.3 Step 3 — sidebar-toggle button must be a sibling of <aside aria-hidden="true">,
|
||||||
|
// not a descendant (finding 09132940 a11y). Hidden-state must not occlude the toggle.
|
||||||
|
test('voyage-playground.html sidebar-toggle is outside aria-hidden region (v4.3 Step 3, finding 09132940)', () => {
|
||||||
|
const text = readFileSync(HTML, 'utf-8');
|
||||||
|
// Find the toggle button and the aside element by their unique anchors.
|
||||||
|
const toggleIdx = text.indexOf('id="voyage-sidebar-toggle"');
|
||||||
|
const asideIdx = text.indexOf('<aside\n id="voyage-sidebar"');
|
||||||
|
// Fallback for single-line aside markup
|
||||||
|
const asideIdxAlt = text.indexOf('<aside id="voyage-sidebar"');
|
||||||
|
const asideAnchor = asideIdx > 0 ? asideIdx : asideIdxAlt;
|
||||||
|
assert.ok(toggleIdx > 0, 'voyage-sidebar-toggle must exist in HTML');
|
||||||
|
assert.ok(asideAnchor > 0, '<aside id="voyage-sidebar"> must exist in HTML');
|
||||||
|
// Toggle must precede the aside element textually
|
||||||
|
assert.ok(toggleIdx < asideAnchor,
|
||||||
|
'voyage-sidebar-toggle (idx ' + toggleIdx + ') must precede <aside id="voyage-sidebar"> (idx ' + asideAnchor + ')');
|
||||||
|
// Regression: slice between the button open-tag and its </button> close-tag,
|
||||||
|
// ensure no <aside element opens inside that slice (would mean the toggle
|
||||||
|
// is nested inside an aside).
|
||||||
|
const toggleBlockEnd = text.indexOf('</button>', toggleIdx);
|
||||||
|
assert.ok(toggleBlockEnd > toggleIdx, 'toggle button must have a </button> closer');
|
||||||
|
const toggleBlock = text.slice(toggleIdx, toggleBlockEnd);
|
||||||
|
assert.doesNotMatch(toggleBlock, /<aside\b/, 'toggle button must NOT contain a nested <aside element');
|
||||||
|
});
|
||||||
|
|
||||||
// v4.3 Step 1 — SC24-security defense in depth: renderArtifact bodyHtml is
|
// v4.3 Step 1 — SC24-security defense in depth: renderArtifact bodyHtml is
|
||||||
// sanitized via DOMPurify before DOM injection (finding 1d3591d4).
|
// sanitized via DOMPurify before DOM injection (finding 1d3591d4).
|
||||||
test('voyage-playground.html renderArtifact sanitizes bodyHtml via DOMPurify (v4.3 Step 1, finding 1d3591d4)', () => {
|
test('voyage-playground.html renderArtifact sanitizes bodyHtml via DOMPurify (v4.3 Step 1, finding 1d3591d4)', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue