Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions client/src/a11yConventions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
* 6. An `<img>` with no `alt`, which is announced by its `src` — a hashed
* filename or a blob URL. `alt=""` is the correct spelling for a
* decorative image and passes; only the omission is the bug.
* 7. An icon-only `<button>` in the MeatSpace health-logging tree sized to
* its bare icon (`p-1` around a 12-14px glyph = a 22px target) instead of
* the 44px floor the rest of the app enforces.
* 7. An icon-only `<button>` sized to its bare icon (`p-1` around a
* 12-14px glyph = a 22px target) instead of the 44px floor the rest of
* the app enforces.
*
* Scoped to git-tracked `.jsx` under `client/src` so an untracked scratch file
* can't fail the suite.
Expand Down Expand Up @@ -3266,7 +3266,7 @@ describe('a11y conventions', () => {
expect(offenders, `Close button under the 44px touch-target minimum — add min-h-[44px] min-w-[44px] + flex items-center justify-center (see Drawer.jsx:106):\n${offenders.join('\n')}`).toEqual([]);
});

it("meets the 44px touch-target minimum on the MeatSpace log-row icon buttons (#5703)", () => {
it("meets the 44px touch-target minimum on icon-only buttons (#5703, #5904)", () => {
// The MeatSpace tabs are the app's most phone-centric surface — a drink or
// a nicotine entry is logged one-handed — and their inline row controls
// kept shipping as a bare `p-1`/`p-1.5` around a 12-14px icon: a 22-26px
Expand All @@ -3277,14 +3277,18 @@ describe('a11y conventions', () => {
// `inline-flex items-center justify-center`), never the icon: icon size is
// what sets the log row's density, and growing it would reflow the tables.
//
// Two scopings, both deliberate. `components/meatspace/` rather than the
// whole tree: the same shape survives in a handful of desktop-first views,
// and widening here would turn one regression guard into a tree-wide sweep.
// And only the `p-0.5`/`p-1`/`p-1.5` shape, rather than every icon button
// missing an explicit min: one that reaches 44px through generous padding
// (`p-3` around a 20px glyph), or that carries no padding class at all, is a
// different question, and folding it in would make the guard report dozens
// of controls this change never looked at.
// Scoped to the whole tree since #5904 swept the same shape outside
// MeatSpace. Only the `p-0.5`/`p-1`/`p-1.5` shape counts, rather than every
// icon button missing an explicit min: one that reaches 44px through
// generous padding (`p-3` around a 20px glyph), or that carries no padding
// class at all, is a different question, and folding it in would make the
// guard report dozens of controls this change never looked at.
//
// Sibling-owned SongBook surfaces are excluded — a parallel change owns
// that tree and sweeps its own buttons.
const isSiblingOwned = (file) => file === "src/pages/SongBook.jsx"
|| file === "src/pages/SongBookViewer.jsx"
|| file.startsWith("src/components/songbook/");
const TIGHT_PADDING = /(?:^|\s)p-(?:0\.5|1|1\.5)(?:\s|$)/;
const offendersIn = (file, src) => {
const out = [];
Expand Down Expand Up @@ -3312,11 +3316,11 @@ describe('a11y conventions', () => {
// from client/), not this file's location — assert the filter really
// selected the tree, so a change to the walker's path shape fails loudly
// here instead of turning the rule into a vacuous pass over zero files.
const scanned = trackedJsxFiles().filter((file) => file.startsWith("src/components/meatspace/"));
expect(scanned.length, "no MeatSpace sources matched — has trackedJsxFiles() changed its path shape?").toBeGreaterThan(20);
const scanned = trackedJsxFiles().filter((file) => !isSiblingOwned(file));
expect(scanned.length, "no client sources matched — has trackedJsxFiles() changed its path shape?").toBeGreaterThan(500);

const offenders = [];
for (const file of scanned) offenders.push(...offendersIn(file, rawSourceOf(file)));
expect(offenders, `MeatSpace icon-only <button> under the 44px touch-target minimum — add min-h-[44px] min-w-[44px] inline-flex items-center justify-center and leave the icon size alone:\n${offenders.join("\n")}`).toEqual([]);
expect(offenders, `Icon-only <button> under the 44px touch-target minimum — add min-h-[44px] min-w-[44px] inline-flex items-center justify-center and leave the icon size alone:\n${offenders.join("\n")}`).toEqual([]);
});
});
2 changes: 1 addition & 1 deletion client/src/components/CatalogCastPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export default function CatalogCastPanel({ refKind, refId, refLabel }) {
onClick={() => handleUnlink(row)}
disabled={rowBusy}
aria-label={`Unlink ${ingredient.name || ingredient.id}`}
className="p-1.5 rounded text-gray-500 hover:text-port-error hover:bg-port-bg disabled:opacity-50"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 rounded text-gray-500 hover:text-port-error hover:bg-port-bg disabled:opacity-50"
title="Unlink from this record"
>
{rowBusy
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/FolderPicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export default function FolderPicker({ value, onChange, defaultPath }) {
<button
type="button"
onClick={() => handleNavigate('~')}
className="p-1 text-gray-500 hover:text-white shrink-0"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-500 hover:text-white shrink-0"
title="Home directory" aria-label="Home directory"
>
<Home size={16} />
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1238,7 +1238,7 @@ export default function Layout() {
<button
type="button"
onClick={() => setCollapsed(true)}
className="hidden lg:flex p-1 text-gray-500 hover:text-white transition-colors"
className="min-h-[44px] min-w-[44px] items-center justify-center hidden lg:flex p-1 text-gray-500 hover:text-white transition-colors"
title="Collapse sidebar"
aria-label="Collapse sidebar"
>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/ThemeSwitcher.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default function ThemeSwitcher({ position = 'above', className = '' }) {
setOpen(true);
}
}}
className="p-1.5 text-gray-500 hover:text-port-accent transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-500 hover:text-port-accent transition-colors"
title="Switch theme"
aria-label={`Switch theme. Current theme: ${theme?.label ?? 'Classic Midnight'}`}
aria-haspopup="menu"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/apps/AppOperationBanner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function AppOperationBanner({ appName, type, steps, error, comple
{onDismiss && (
<button
onClick={onDismiss}
className="shrink-0 p-1 rounded text-gray-400 hover:text-white focus:outline-hidden focus:ring-2 focus:ring-port-accent"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center shrink-0 p-1 rounded text-gray-400 hover:text-white focus:outline-hidden focus:ring-2 focus:ring-port-accent"
aria-label={`Dismiss ${name} operation status`}
>
<X size={16} aria-hidden="true" />
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/apps/EditAppDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ export default function EditAppDrawer({ app, onClose, onSave }) {
<button
type="button"
onClick={() => copyToClipboard(tlsResult.snippet, 'Snippet copied')}
className="absolute top-1 right-1 p-1 bg-port-border/60 hover:bg-port-border rounded"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center absolute top-1 right-1 p-1 bg-port-border/60 hover:bg-port-border rounded"
aria-label="Copy snippet"
>
<Copy size={12} />
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/brain/links/LinkChip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function LinkChip({ link, onRemove, draggable }) {
{onRemove && (
<button
onClick={() => onRemove(link)}
className="shrink-0 p-0.5 text-gray-600 hover:text-port-error opacity-40 sm:opacity-0 sm:group-hover:opacity-100 focus-visible:opacity-100 transition-opacity"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center shrink-0 p-0.5 text-gray-600 hover:text-port-error opacity-40 sm:opacity-0 sm:group-hover:opacity-100 focus-visible:opacity-100 transition-opacity"
title="Remove from bucket" aria-label="Remove from bucket"
>
<X size={13} />
Expand Down
22 changes: 11 additions & 11 deletions client/src/components/brain/tabs/InboxTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ export default function InboxTab({ onRefresh, settings }) {
<button
type="button"
onClick={() => { fetchInbox(); onRefresh?.(); }}
className="p-1 text-gray-400 hover:text-white transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-400 hover:text-white transition-colors"
title="Refresh inbox"
aria-label="Refresh inbox"
>
Expand Down Expand Up @@ -471,7 +471,7 @@ export default function InboxTab({ onRefresh, settings }) {
rows={3}
autoFocus
/>
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-2">
<button
onClick={() => handleSaveEdit(entry.id)}
className="p-1 min-h-[44px] min-w-[44px] flex items-center justify-center text-port-success hover:bg-port-success/20 rounded transition-colors"
Expand All @@ -491,7 +491,7 @@ export default function InboxTab({ onRefresh, settings }) {
) : (
<>
<p className="text-white flex-1">{entry.capturedText}</p>
<div className="flex gap-1">
<div className="flex gap-2">
<button
onClick={() => handleEdit(entry)}
className="p-1 min-h-[44px] min-w-[44px] flex items-center justify-center text-gray-400 hover:text-white transition-colors"
Expand Down Expand Up @@ -645,17 +645,17 @@ export default function InboxTab({ onRefresh, settings }) {
rows={3}
autoFocus
/>
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-2">
<button
onClick={() => handleSaveEdit(entry.id)}
className="p-1 text-port-success hover:bg-port-success/20 rounded transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-port-success hover:bg-port-success/20 rounded transition-colors"
title="Save changes" aria-label="Save changes"
>
<Save size={14} />
</button>
<button
onClick={handleCancelEdit}
className="p-1 text-gray-400 hover:bg-port-border/50 rounded transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-400 hover:bg-port-border/50 rounded transition-colors"
title="Cancel editing" aria-label="Cancel editing"
>
<X size={14} />
Expand All @@ -665,17 +665,17 @@ export default function InboxTab({ onRefresh, settings }) {
) : (
<>
<p className="text-white flex-1">{entry.capturedText}</p>
<div className="flex gap-1">
<div className="flex gap-2">
<button
onClick={() => handleEdit(entry)}
className="p-1 text-gray-400 hover:text-white transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-400 hover:text-white transition-colors"
title="Edit text" aria-label="Edit text"
>
<Edit2 size={14} />
</button>
<button
onClick={() => setConfirmingDeleteId(entry.id)}
className="p-1 text-gray-400 hover:text-port-error transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-400 hover:text-port-error transition-colors"
title="Delete entry" aria-label="Delete entry"
>
<Trash2 size={14} />
Expand Down Expand Up @@ -763,7 +763,7 @@ export default function InboxTab({ onRefresh, settings }) {
<div className="flex gap-1">
<button
onClick={() => setConfirmingDeleteId(entry.id)}
className="p-1 text-gray-400 hover:text-port-error transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-400 hover:text-port-error transition-colors"
title="Delete entry" aria-label="Delete entry"
>
<Trash2 size={14} />
Expand Down Expand Up @@ -909,7 +909,7 @@ export default function InboxTab({ onRefresh, settings }) {
<div className="flex gap-1">
<button
onClick={() => setConfirmingDeleteId(entry.id)}
className="p-1 text-gray-500 hover:text-port-error transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1 text-gray-500 hover:text-port-error transition-colors"
title="Delete entry" aria-label="Delete entry"
>
<Trash2 size={14} />
Expand Down
8 changes: 4 additions & 4 deletions client/src/components/brain/tabs/LinksTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ export default function LinksTab({ onRefresh }) {
{search && (
<button
onClick={() => setSearch('')}
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-white transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center absolute right-2 top-1/2 -translate-y-1/2 p-1 text-gray-500 hover:text-white transition-colors"
title="Clear search" aria-label="Clear search"
>
<X size={14} />
Expand Down Expand Up @@ -760,17 +760,17 @@ export default function LinksTab({ onRefresh }) {
)}
</div>

<div className="flex items-center gap-1 shrink-0">
<div className="flex items-center gap-2 shrink-0">
<button
onClick={() => handleEdit(link)}
className="p-1.5 text-gray-400 hover:text-white transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-400 hover:text-white transition-colors"
title="Edit" aria-label="Edit"
>
<Edit2 size={14} />
</button>
<button
onClick={() => setConfirmingDeleteId(link.id)}
className="p-1.5 text-gray-400 hover:text-port-error transition-colors"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-400 hover:text-port-error transition-colors"
title="Delete" aria-label="Delete"
>
<Trash2 size={14} />
Expand Down
10 changes: 5 additions & 5 deletions client/src/components/brain/tabs/MemoryTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -674,34 +674,34 @@ export default function MemoryTab({ onRefresh, fixedType = null }) {
</div>
</div>

<div className="flex items-center gap-1">
<div className="flex items-center gap-2">
{(activeType === 'projects' || activeType === 'ideas' || activeType === 'admin') && record.status !== 'done' && (
<button
onClick={() => handleMarkDone(record)}
className="p-1.5 text-gray-400 hover:text-port-success rounded hover:bg-port-success/20"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-400 hover:text-port-success rounded hover:bg-port-success/20"
title="Mark done" aria-label="Mark done"
>
<CheckCircle2 size={14} />
</button>
)}
<button
onClick={() => handleSendToCatalog(record)}
className="p-1.5 text-gray-400 hover:text-port-accent-2 rounded hover:bg-port-accent-2/20"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-400 hover:text-port-accent-2 rounded hover:bg-port-accent-2/20"
title="Send to Catalog"
aria-label="Send to Catalog"
>
<Library size={14} />
</button>
<button
onClick={() => startEdit(record)}
className="p-1.5 text-gray-400 hover:text-white rounded hover:bg-port-border/50"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-400 hover:text-white rounded hover:bg-port-border/50"
title="Edit" aria-label="Edit"
>
<Edit2 size={14} />
</button>
<button
onClick={() => requestDelete(record.id)}
className="p-1.5 text-gray-400 hover:text-port-error rounded hover:bg-port-error/20"
className="min-h-[44px] min-w-[44px] inline-flex items-center justify-center p-1.5 text-gray-400 hover:text-port-error rounded hover:bg-port-error/20"
title="Delete" aria-label="Delete"
>
<Trash2 size={14} />
Expand Down
Loading