From 5836e48a631410c571e105e3f8fb23ebc83ae34a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:26:57 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20aria-controls?= =?UTF-8?q?=20to=20change=20request=20accordion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: brycejohnson1417 <257422776+brycejohnson1417@users.noreply.github.com> --- .Jules/palette.md | 3 + .../change-requests/change-request-list.tsx | 511 +++++++++++------- 2 files changed, 304 insertions(+), 210 deletions(-) create mode 100644 .Jules/palette.md diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..6eae0c0 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 2024-07-01 - Add aria-controls to conditionally rendered accordions +**Learning:** When an accordion's content is conditionally rendered in React (e.g., `{expanded ?
...
: null}`), adding `aria-controls` to the trigger button will point to a non-existent ID when collapsed, breaking screen reader association. +**Action:** Always provide an empty persistent container with the `id` (e.g., `
{expanded ? : null}
`) or apply the `id` to both branches of a ternary to ensure the association remains unbroken. \ No newline at end of file diff --git a/components/change-requests/change-request-list.tsx b/components/change-requests/change-request-list.tsx index e349e00..abcfa8d 100644 --- a/components/change-requests/change-request-list.tsx +++ b/components/change-requests/change-request-list.tsx @@ -121,12 +121,16 @@ function buildRequestTitle(problem: string, fallback: string) { return trimmed.length > 80 ? `${trimmed.slice(0, 79)}…` : trimmed; } -async function readMutationResponse(response: Response): Promise { +async function readMutationResponse( + response: Response, +): Promise { const text = await response.text(); if (!text) { return { ok: false, - error: response.ok ? "The server returned an empty response." : `The server returned ${response.status}.`, + error: response.ok + ? "The server returned an empty response." + : `The server returned ${response.status}.`, }; } @@ -135,14 +139,18 @@ async function readMutationResponse(response: Response): Promise(null); const [drafts, setDrafts] = useState>({}); const [busyRequestId, setBusyRequestId] = useState(null); - const [errorByRequest, setErrorByRequest] = useState>({}); + const [errorByRequest, setErrorByRequest] = useState< + Record + >({}); const [notice, setNotice] = useState(null); useEffect(() => { @@ -209,7 +219,13 @@ export function ChangeRequestList({ setDrafts((current) => ({ ...current, [requestId]: { - ...(current[requestId] ?? { problem: "", requestedOutcome: "", businessContext: "", acceptanceCriteria: "", attachments: [] }), + ...(current[requestId] ?? { + problem: "", + requestedOutcome: "", + businessContext: "", + acceptanceCriteria: "", + attachments: [], + }), ...patch, }, })); @@ -256,7 +272,10 @@ export function ChangeRequestList({ }, ); } catch (error) { - if (draft.attachments.length && isRetriableAttachmentTransportError(error)) { + if ( + draft.attachments.length && + isRetriableAttachmentTransportError(error) + ) { response = await fetch( `/api/runtime/organizations/${encodeURIComponent(orgSlug)}/change-requests/${encodeURIComponent(request.id)}`, { @@ -264,7 +283,8 @@ export function ChangeRequestList({ body: buildFormData([]), }, ); - attachmentFallbackNotice = "Request updated. Attachments were skipped because this browser could not upload them."; + attachmentFallbackNotice = + "Request updated. Attachments were skipped because this browser could not upload them."; } else { throw error; } @@ -275,17 +295,22 @@ export function ChangeRequestList({ throw new Error(payload.error ?? "Unable to save request."); } - setRequests((current) => current.map((item) => (item.id === request.id ? payload.request! : item))); + setRequests((current) => + current.map((item) => + item.id === request.id ? payload.request! : item, + ), + ); cancelEditing(request.id); setNotice( payload.warnings?.length ? `Request updated. ${payload.warnings.join(" ")}` - : attachmentFallbackNotice ?? "Request updated.", + : (attachmentFallbackNotice ?? "Request updated."), ); } catch (error) { setErrorByRequest((current) => ({ ...current, - [request.id]: error instanceof Error ? error.message : "Unable to save request.", + [request.id]: + error instanceof Error ? error.message : "Unable to save request.", })); } finally { setBusyRequestId(null); @@ -307,12 +332,17 @@ export function ChangeRequestList({ method: "DELETE", }, ); - const payload = (await response.json()) as { ok: boolean; error?: string }; + const payload = (await response.json()) as { + ok: boolean; + error?: string; + }; if (!response.ok || !payload.ok) { throw new Error(payload.error ?? "Unable to delete request."); } - setRequests((current) => current.filter((request) => request.id !== requestId)); + setRequests((current) => + current.filter((request) => request.id !== requestId), + ); setExpandedIds((current) => { const next = { ...current }; delete next[requestId]; @@ -323,7 +353,8 @@ export function ChangeRequestList({ } catch (error) { setErrorByRequest((current) => ({ ...current, - [requestId]: error instanceof Error ? error.message : "Unable to delete request.", + [requestId]: + error instanceof Error ? error.message : "Unable to delete request.", })); } finally { setBusyRequestId(null); @@ -334,10 +365,15 @@ export function ChangeRequestList({
-

Change requests

-

Workspace request queue

+

+ Change requests +

+

+ Workspace request queue +

- Each marked comment lands here as its own request. Open a request to add more detail, attach files, or delete it. + Each marked comment lands here as its own request. Open a request to + add more detail, attach files, or delete it.

@@ -347,7 +383,9 @@ export function ChangeRequestList({
- {expanded ? ( -
- {previewImage?.signedUrl ? ( - - {`${request.title} - - ) : null} - - {editing ? ( -
-