Skip to content

Commit 7a96d64

Browse files
TonybynMp4claude
andcommitted
fix(web): snooze times read naturally in context menus
Snooze presets baked the time into the label as "In 1 hour (3:00 PM)". Context menu items now take an optional detail: the web fallback renders it as a muted, right-aligned column and names the item "label, detail" for screen readers, while native Electron menus keep "label (detail)". Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent d0b9e9d commit 7a96d64

6 files changed

Lines changed: 30 additions & 3 deletions

File tree

‎apps/desktop/src/electron/ElectronMenu.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM
7575

7676
const normalizedItem: ContextMenuItem = {
7777
id: sourceItem.id,
78-
label: sourceItem.label,
78+
label:
79+
typeof sourceItem.detail === "string" && sourceItem.detail.length > 0
80+
? `${sourceItem.label} (${sourceItem.detail})`
81+
: sourceItem.label,
7982
destructive: sourceItem.destructive === true,
8083
disabled: sourceItem.disabled === true,
8184
...(sourceItem.separatorBefore === true ? { separatorBefore: true } : {}),

‎apps/web/src/components/Sidebar.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2108,7 +2108,8 @@ export default function Sidebar({ routeReveal }: { routeReveal: SidebarRouteReve
21082108
children: [
21092109
...snoozePresets.map((preset) => ({
21102110
id: `snooze:${preset.id}`,
2111-
label: `${preset.label} (${preset.whenLabel})`,
2111+
label: preset.label,
2112+
detail: preset.whenLabel,
21122113
disabled: !canOperateThreads(selectedThreads),
21132114
})),
21142115
{

‎apps/web/src/components/threadActionMenu.logic.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ export function buildThreadActionMenuItems(
144144
children: [
145145
...state.snoozePresets.map((preset) => ({
146146
id: `snooze:${preset.id}` as const,
147-
label: `${preset.label} (${preset.whenLabel})`,
147+
label: preset.label,
148+
detail: preset.whenLabel,
148149
})),
149150
{ id: "snooze:custom" as const, label: "Custom…", separatorBefore: true },
150151
],

‎apps/web/src/contextMenuFallback.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,12 @@ describe("showContextMenuFallback", () => {
235235

236236
await expect(selectionPromise).resolves.toBe("rename");
237237
});
238+
239+
it("shows an item's detail beside its label and reads both to screen readers", () => {
240+
void showContextMenuFallback([{ id: "snooze:hour", label: "In 1 hour", detail: "3:00 PM" }]);
241+
const button = findButton("In 1 hour");
242+
243+
expect(button?.textContent).toBe("In 1 hour3:00 PM");
244+
expect(button?.attributes.get("aria-label")).toBe("In 1 hour, 3:00 PM");
245+
});
238246
});

‎apps/web/src/contextMenuFallback.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,17 @@ export function showContextMenuFallback<T extends string>(
371371
label.textContent = item.label;
372372
button.appendChild(label);
373373

374+
if (item.detail) {
375+
// The comma gives screen readers a pause between label and detail.
376+
button.setAttribute("aria-label", `${item.label}, ${item.detail}`);
377+
const detail = document.createElement("span");
378+
detail.className = "ms-auto shrink-0 ps-4 text-muted-foreground text-xs";
379+
detail.style.cssText =
380+
"margin-inline-start:auto;flex-shrink:0;padding-inline-start:1rem;color:var(--contrast-muted-foreground);font-size:0.75rem;";
381+
detail.textContent = item.detail;
382+
button.appendChild(detail);
383+
}
384+
374385
if (hasChildren) {
375386
button.setAttribute("aria-haspopup", "menu");
376387
button.setAttribute("aria-expanded", "false");

‎packages/contracts/src/ipc.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
export interface ContextMenuItem<T extends string = string> {
2525
id: T;
2626
label: string;
27+
detail?: string;
2728
destructive?: boolean;
2829
disabled?: boolean;
2930
header?: boolean;
@@ -40,6 +41,7 @@ export type QuitShortcutHintEvent =
4041
export interface ContextMenuItemSchemaType {
4142
readonly id: string;
4243
readonly label: string;
44+
readonly detail?: string;
4345
readonly destructive?: boolean;
4446
readonly disabled?: boolean;
4547
readonly header?: boolean;
@@ -52,6 +54,7 @@ export interface ContextMenuItemSchemaType {
5254
export const ContextMenuItemSchema: Schema.Codec<ContextMenuItemSchemaType> = Schema.Struct({
5355
id: Schema.String,
5456
label: Schema.String,
57+
detail: Schema.optionalKey(Schema.String),
5558
destructive: Schema.optionalKey(Schema.Boolean),
5659
disabled: Schema.optionalKey(Schema.Boolean),
5760
header: Schema.optionalKey(Schema.Boolean),

0 commit comments

Comments
 (0)