diff --git a/apps/api/src/pages/tasks.service.ts b/apps/api/src/pages/tasks.service.ts index 3298de6..2a58587 100644 --- a/apps/api/src/pages/tasks.service.ts +++ b/apps/api/src/pages/tasks.service.ts @@ -171,7 +171,8 @@ export class TasksService { } return ( `` + - `` + + `` + + `` + `` + `` + `${rows.join('')}
${escapeHtml(t('colTask'))}${escapeHtml(t('colMentions'))}${escapeHtml(t('colDone'))}${escapeHtml(t('colTask'))}${escapeHtml(t('colMentions'))}${escapeHtml(t('colStart'))}${escapeHtml(t('colDue'))}${escapeHtml(t('colPage'))}
` diff --git a/apps/web/src/api-tokens/ApiTokensSection.tsx b/apps/web/src/api-tokens/ApiTokensSection.tsx index d7895bf..22d0a9d 100644 --- a/apps/web/src/api-tokens/ApiTokensSection.tsx +++ b/apps/web/src/api-tokens/ApiTokensSection.tsx @@ -182,7 +182,9 @@ function TokenList({ tokens }: { tokens: ApiTokenView[] }): React.JSX.Element { {t('list.lastUsed')} {t('list.expires')} {t('list.status')} - + + {t('common:tableActions')} + diff --git a/apps/web/src/api-tokens/FeedTokensSection.tsx b/apps/web/src/api-tokens/FeedTokensSection.tsx index 30256d5..1b2e40d 100644 --- a/apps/web/src/api-tokens/FeedTokensSection.tsx +++ b/apps/web/src/api-tokens/FeedTokensSection.tsx @@ -91,7 +91,9 @@ export function FeedTokensSection(): React.JSX.Element { {t('fields.name')} {t('list.created')} {t('list.lastUsed')} - + + {t('common:tableActions')} + diff --git a/apps/web/src/components/forms.tsx b/apps/web/src/components/forms.tsx index ea9626c..c16a986 100644 --- a/apps/web/src/components/forms.tsx +++ b/apps/web/src/components/forms.tsx @@ -1,3 +1,4 @@ +import { cloneElement, isValidElement, useId } from 'react'; import { useTranslation } from 'react-i18next'; import { ApiError } from '../lib/api'; @@ -20,13 +21,28 @@ export function Field({ children: React.ReactNode; }): React.JSX.Element { const { t } = useTranslation(); + const noteId = useId(); + // Tie the hint/error text to the control itself (#168, WCAG 3.3.1): + // screen readers then repeat it when the field receives focus. Only a + // single element child can be wired; fragments render unchanged. + const wired = + isValidElement(children) && (error || hint) + ? cloneElement(children as React.ReactElement>, { + 'aria-describedby': noteId, + ...(error ? { 'aria-invalid': true } : {}), + }) + : children; return (