feat(voyage): implement hidden-by-default sidebar-rail with ordered list + filter + jumplist count

Step 19 of v4.3 playground plan. Sidebar now default aria-hidden=true
(translateX collapses panel, leaves 40px FAB rail). FAB toggle has
data-action=toggle-sidebar for keyboard binding (] in Step 20).

New annotation-list section in sidebar panel:
  - filter radiogroup: Alle (default), Åpne (unresolved), Resolved
  - voyage-jumplist ordered list with numbered badges matching the
    gutter-badge ordering (sorted by line ASC)
  - aria-live jumplist count: "X av N" (filtered/total)
  - click list-item -> setActiveAnchor + scrollIntoView + data-active

renderAnnotationList wires into mountRender so list refreshes on every
render. Filter state (voyageFilterState) persists across renders within
the session.

Trace: SC6, research/04 Dim 1 (hidden-by-default) + Insight 1 +
Recommendation sidebar/navigation.
This commit is contained in:
Kjell Tore Guttormsen 2026-05-10 17:09:26 +02:00
commit 6db7c72511
2 changed files with 201 additions and 9 deletions

View file

@ -250,3 +250,32 @@ test('voyage-playground.html declares injectAnchorBadges JS function (v4.3 Step
const text = readFileSync(HTML, 'utf-8');
assert.match(text, /function\s+injectAnchorBadges\s*\(\s*\)/, 'injectAnchorBadges() function required');
});
test('voyage-playground.html declares voyage-sidebar hidden-by-default (v4.3 Step 19)', () => {
const text = readFileSync(HTML, 'utf-8');
assert.match(text, /id="voyage-sidebar"[\s\S]{0,200}aria-hidden="true"/, 'voyage-sidebar must default aria-hidden="true"');
});
test('voyage-playground.html declares data-action="toggle-sidebar" on FAB (v4.3 Step 19)', () => {
const text = readFileSync(HTML, 'utf-8');
assert.match(text, /data-action="toggle-sidebar"/, 'data-action="toggle-sidebar" required on FAB toggle button');
});
test('voyage-playground.html declares voyage-jumplist + count "X av N" (v4.3 Step 19)', () => {
const text = readFileSync(HTML, 'utf-8');
assert.match(text, /id="voyage-jumplist"/, 'voyage-jumplist ordered list required');
assert.match(text, /id="voyage-jumplist-count"/, 'voyage-jumplist-count container required');
assert.match(text, /' av '/, '"X av N" jumplist count format string required in JS');
});
test('voyage-playground.html declares filter-buttons Alle/Åpne/Resolved (v4.3 Step 19)', () => {
const text = readFileSync(HTML, 'utf-8');
assert.match(text, /data-filter="all"/, 'filter button data-filter="all" required');
assert.match(text, /data-filter="open"/, 'filter button data-filter="open" required');
assert.match(text, /data-filter="resolved"/, 'filter button data-filter="resolved" required');
});
test('voyage-playground.html declares renderAnnotationList JS function (v4.3 Step 19)', () => {
const text = readFileSync(HTML, 'utf-8');
assert.match(text, /function\s+renderAnnotationList\s*\(\s*\)/, 'renderAnnotationList() function required');
});