personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

a11y: add keyboard access to support ticket cards and drop zone

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+20 -2
+20 -2
apps/support/workspace.html
··· 34 34 transition: background 0.1s; 35 35 } 36 36 .support-ticket:hover { background: var(--card-bg, #fafafa); } 37 + .support-ticket:focus-visible { outline: 2px solid var(--facet-color, #3b82f6); outline-offset: 2px; } 37 38 .support-ticket-header { 38 39 display: flex; 39 40 justify-content: space-between; ··· 189 190 background: rgba(59, 130, 246, 0.04); 190 191 color: var(--text, #222); 191 192 } 193 + .support-drop-zone:focus-visible { outline: 2px solid var(--facet-color, #3b82f6); outline-offset: 2px; } 192 194 .support-drop-zone input[type="file"] { display: none; } 193 195 .support-file-list { 194 196 margin-top: 0.5rem; ··· 348 350 349 351 list.innerHTML = tickets.map(t => { 350 352 const statusClass = 'support-status-' + (t.status || 'open').replace(/[^a-z-]/g, ''); 351 - return `<div class="support-ticket" data-id="${t.id}"> 353 + return `<div class="support-ticket" data-id="${t.id}" tabindex="0" role="button"> 352 354 <div class="support-ticket-header"> 353 355 <span class="support-ticket-subject">${esc(t.subject || 'Untitled')}</span> 354 356 <span class="support-status ${statusClass}">${esc(t.status || 'open')}</span> ··· 365 367 list.querySelectorAll('.support-ticket').forEach(el => { 366 368 el.addEventListener('click', () => openTicket(parseInt(el.dataset.id))); 367 369 }); 370 + list.querySelectorAll('.support-ticket').forEach(el => { 371 + el.addEventListener('keydown', e => { 372 + if (e.key === 'Enter' || e.key === ' ') { 373 + e.preventDefault(); 374 + openTicket(parseInt(el.dataset.id)); 375 + } 376 + }); 377 + }); 368 378 } catch (e) { 369 379 list.innerHTML = '<div class="support-empty"><p>Unable to load tickets.</p></div>'; 370 380 } ··· 415 425 if (t.status !== 'resolved') { 416 426 html += `<div class="support-reply-form"> 417 427 <textarea id="reply-text" placeholder="Write a reply..."></textarea> 418 - <div class="support-drop-zone" id="attach-zone"> 428 + <div class="support-drop-zone" id="attach-zone" role="button" tabindex="0" aria-label="Attach files — drop files here or click to browse"> 419 429 <input type="file" id="attach-input" multiple accept=".png,.jpg,.jpeg,.gif,.webp,.svg,.pdf,.txt,.csv,.html,.md,.xml,.json"> 420 430 Drop files here or click to attach (max 10 MB each, up to 5 files) 421 431 </div> ··· 435 445 detail.innerHTML = ''; 436 446 list.style.display = ''; 437 447 }); 448 + const backBtn = document.getElementById('back-to-list'); 449 + if (backBtn) backBtn.focus(); 438 450 439 451 // -- Attachment handling -- 440 452 let pendingFiles = []; ··· 484 496 485 497 if (zone) { 486 498 zone.addEventListener('click', () => fileInput && fileInput.click()); 499 + zone.addEventListener('keydown', e => { 500 + if (e.key === 'Enter' || e.key === ' ') { 501 + e.preventDefault(); 502 + fileInput && fileInput.click(); 503 + } 504 + }); 487 505 zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); }); 488 506 zone.addEventListener('dragleave', () => zone.classList.remove('dragover')); 489 507 zone.addEventListener('drop', e => {