Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,6 @@
## 2026-07-01 - Prevent Focus Stealing in Search Filter Buttons
**Learning:** In hybrid mouse/keyboard search interfaces (like VS Code Webviews), clicking interactive elements like filter buttons steals DOM focus from the primary search input. This forces users to manually click back into the input or hit Tab before they can resume typing.
**Action:** When implementing filter or scope toggle buttons that don't need their own text-input focus, attach a `mousedown` event listener that calls `e.preventDefault()`. This prevents the browser's default focus transition before it even fires, ensuring the user's cursor remains firmly anchored in the main search input.
## 2026-07-02 - ARIA Combobox for Search Results
**Learning:** In custom Webview search interfaces, using keyboard arrow keys to navigate a list of `.result-item` divs visually highlights them, but screen readers remain completely silent because focus never actually moves to the elements.
**Action:** When implementing arrow key navigation for a search input, always use the ARIA Combobox pattern: apply `role="combobox"`, `aria-expanded`, and `aria-activedescendant` to the input, pair it with a `role="listbox"`, and give each result item `role="option"` with a unique ID to ensure screen readers announce the highlighted item.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ suite('ReferenceCodeLens Test Suite', () => {
assert.ok(Array.isArray(lenses), 'Should return an array even on error');
});

test('Provider should provide code lenses for supported symbol kinds', async () => {
test('Provider should provide code lenses for supported symbol kinds', async function () { this.timeout(5000);
// Increase timeout for this test as the TS language server can be slow to initialize in CI
// Create a test document with a class
const testContent = `
Expand Down
26 changes: 23 additions & 3 deletions vscode-extension/src/webviews/search-view.html
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,12 @@
<button class="scope-button" data-scope="properties" title="Search Properties" aria-pressed="false"><i class="codicon codicon-symbol-property"></i> Properties</button>
<button class="scope-button" data-scope="endpoints" title="Search API Endpoints" aria-pressed="false"><i class="codicon codicon-globe"></i> Endpoints</button>
</div>
<input type="text" id="search-input" placeholder="Search everywhere..." autocomplete="off" spellcheck="false" aria-label="Search everywhere">
<input type="text" id="search-input" placeholder="Search everywhere..." autocomplete="off" spellcheck="false" aria-label="Search everywhere" role="combobox" aria-expanded="false" aria-autocomplete="list" aria-controls="results">
<div class="progress-bar-container" id="loading-indicator" aria-hidden="true"><div class="progress-bar"></div></div>

<div id="search-summary" class="search-summary" aria-live="polite" aria-atomic="true"></div>
</div>
<div id="results" class="results-container"></div>
<div id="results" class="results-container" role="listbox" aria-label="Search Results"></div>

<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
Expand Down Expand Up @@ -621,11 +621,16 @@
renderHistoryToolbar();
}

resultsContainer.setAttribute('aria-expanded', results.length > 0 ? 'true' : 'false');
document.getElementById('search-input').setAttribute('aria-expanded', results.length > 0 ? 'true' : 'false');

results.forEach((result, index) => {
const item = result.item;
const div = document.createElement('div');
div.className = 'result-item';
div.setAttribute('role', 'option');
div.id = 'result-item-' + index;
div.setAttribute('aria-selected', 'false');

// Row 1: icon + label + description
const row1 = document.createElement('div');
Expand Down Expand Up @@ -722,9 +727,15 @@
}

function renderSlashCommands() {
resultsContainer.setAttribute('aria-expanded', slashCommands.length > 0 ? 'true' : 'false');
document.getElementById('search-input').setAttribute('aria-expanded', slashCommands.length > 0 ? 'true' : 'false');

slashCommands.forEach((command, index) => {
const div = document.createElement('div');
div.className = 'result-item';
div.setAttribute('role', 'option');
div.id = 'slash-item-' + index;
div.setAttribute('aria-selected', 'false');

// Row 1: icon + label + description
const row1 = document.createElement('div');
Expand Down Expand Up @@ -791,6 +802,8 @@

function renderEmptyState() {
resultsContainer.textContent = '';
resultsContainer.setAttribute('aria-expanded', 'false');
document.getElementById('search-input').setAttribute('aria-expanded', 'false');

const messageDiv = document.createElement('div');
messageDiv.className = 'empty-state';
Expand Down Expand Up @@ -1163,11 +1176,18 @@

function selectItem(index) {
const items = document.querySelectorAll('.result-item');
if (selectedIndex >= 0) items[selectedIndex].classList.remove('selected');
if (selectedIndex >= 0) {
items[selectedIndex].classList.remove('selected');
items[selectedIndex].setAttribute('aria-selected', 'false');
}
selectedIndex = index;
if (selectedIndex >= 0) {
items[selectedIndex].classList.add('selected');
items[selectedIndex].setAttribute('aria-selected', 'true');
items[selectedIndex].scrollIntoView({ block: 'nearest' });
document.getElementById('search-input').setAttribute('aria-activedescendant', items[selectedIndex].id);
} else {
document.getElementById('search-input').removeAttribute('aria-activedescendant');
}
}

Expand Down
Loading