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 (
`
` +
- ` | ${escapeHtml(t('colTask'))} | ${escapeHtml(t('colMentions'))} | ` +
+ `${escapeHtml(t('colDone'))} | ` +
+ `${escapeHtml(t('colTask'))} | ${escapeHtml(t('colMentions'))} | ` +
`${escapeHtml(t('colStart'))} | ${escapeHtml(t('colDue'))} | ` +
`${escapeHtml(t('colPage'))} | ` +
`
${rows.join('')}
`
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 (