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({