From 33cae9bb948108507e4f0145ef8ec10c79c55330 Mon Sep 17 00:00:00 2001 From: Molecule AI Frontend Engineer Date: Sat, 18 Apr 2026 01:14:35 +0000 Subject: [PATCH] =?UTF-8?q?fix(a11y):=20MemoryInspectorPanel=20=E2=80=94?= =?UTF-8?q?=20sanitise=20bodyId,=20add=20aria-controls=20(#904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Memory keys can contain characters like [ ] / : . # and spaces that make invalid HTML id values (breaks CSS selectors and ARIA id-ref lookups). - Add sanitizeId() helper: replaces non-alphanumeric chars with hyphens, collapses consecutive hyphens, strips leading/trailing hyphens - Compute bodyId = "mem-body-{sanitizeId(entry.key)}" in MemoryEntryRow - Set id={bodyId} on the expanded body container - Set aria-controls={bodyId} on the toggle button so AT can navigate directly between the button and its controlled panel Closes #904 Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/MemoryInspectorPanel.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/canvas/src/components/MemoryInspectorPanel.tsx b/canvas/src/components/MemoryInspectorPanel.tsx index b52bbdeb..506c70d3 100644 --- a/canvas/src/components/MemoryInspectorPanel.tsx +++ b/canvas/src/components/MemoryInspectorPanel.tsx @@ -27,6 +27,19 @@ interface Props { // ── Helpers ─────────────────────────────────────────────────────────────────── +/** + * Sanitise a memory key for use in an HTML id attribute. + * HTML IDs must not contain whitespace; many non-alphanumeric characters also + * cause selector or ARIA failures. Replace every non-alphanumeric character + * with a hyphen, collapse consecutive hyphens, then strip leading/trailing ones. + */ +function sanitizeId(key: string): string { + return key + .replace(/[^a-zA-Z0-9]/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, ""); +} + function formatRelativeTime(iso: string): string { const diff = Date.now() - new Date(iso).getTime(); if (diff < 60_000) return `${Math.floor(diff / 1000)}s`; @@ -280,6 +293,10 @@ function MemoryEntryRow({ onCancelEdit, onDelete, }: MemoryEntryRowProps) { + // Sanitise the key so the generated id is a valid HTML id (no spaces or + // special chars like [ ] / : . # that would break CSS selectors / ARIA). + const bodyId = `mem-body-${sanitizeId(entry.key)}`; + return (
{/* Header row — click to expand/collapse */} @@ -287,6 +304,7 @@ function MemoryEntryRow({ className="w-full flex items-center gap-2 px-3 py-2.5 text-left hover:bg-zinc-800/30 transition-colors" onClick={onToggle} aria-expanded={isExpanded} + aria-controls={bodyId} > {entry.key} @@ -304,7 +322,7 @@ function MemoryEntryRow({ {/* Expanded body */} {isExpanded && ( -
+
{entry.expires_at && (

Expires: {new Date(entry.expires_at).toLocaleString()}