diff --git a/apps/dashboard/src/InboxPanel.tsx b/apps/dashboard/src/InboxPanel.tsx
index ed4b76d..ca66edf 100644
--- a/apps/dashboard/src/InboxPanel.tsx
+++ b/apps/dashboard/src/InboxPanel.tsx
@@ -6,9 +6,10 @@ import type {
DashboardTask,
DashboardTaskAction,
} from './api';
-import { formatDate, taskActionsFor } from './dashboardHelpers';
+import { formatDate } from './dashboardHelpers';
import { EmptyState } from './EmptyState';
import type { Locale, Messages } from './i18n';
+import { TaskActionButtons } from './TaskActionButtons';
export type InboxItem = DashboardOverview['inbox'][number];
export type InboxActionKey = `${string}:${DashboardInboxAction}`;
@@ -103,7 +104,6 @@ function InboxCard({
item.source === 'scheduled-task' && item.taskId
? tasks.find((task) => task.id === item.taskId)
: undefined;
- const linkedTaskActions = linkedTask ? taskActionsFor(linkedTask) : [];
const inboxActions = inboxActionsFor(item);
return (
@@ -148,25 +148,14 @@ function InboxCard({
{item.taskId ? t.inbox.openTask : t.inbox.openRoom}
) : null}
- {linkedTask && linkedTaskActions.length > 0 ? (
-
- {linkedTaskActions.map((action) => {
- const actionKey = `${linkedTask.id}:${action}`;
- const busy = taskActionKey === actionKey;
- return (
-
- );
- })}
-
+ {linkedTask ? (
+
diff --git a/apps/dashboard/src/TaskActionButtons.test.ts b/apps/dashboard/src/TaskActionButtons.test.ts
new file mode 100644
index 0000000..5a000da
--- /dev/null
+++ b/apps/dashboard/src/TaskActionButtons.test.ts
@@ -0,0 +1,50 @@
+import { createElement } from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it } from 'vitest';
+
+import type { DashboardTask } from './api';
+import { messages } from './i18n';
+import { TaskActionButtons } from './TaskActionButtons';
+
+const t = messages.en;
+
+const task: DashboardTask = {
+ agentType: 'codex',
+ chatJid: 'room-1',
+ ciMetadata: null,
+ ciProvider: null,
+ contextMode: 'group',
+ createdAt: '2026-04-28T04:00:00.000Z',
+ groupFolder: 'eyejokerdb',
+ id: 'task-1',
+ isWatcher: false,
+ lastResult: null,
+ lastRun: null,
+ nextRun: '2026-04-28T05:00:00.000Z',
+ promptLength: 42,
+ promptPreview: 'Run production check',
+ scheduleType: 'interval',
+ scheduleValue: '10m',
+ status: 'active',
+ suspendedUntil: null,
+};
+
+describe('TaskActionButtons', () => {
+ it('renders task actions with shared busy state styling', () => {
+ const html = renderToStaticMarkup(
+ createElement(TaskActionButtons, {
+ className: 'inbox-actions',
+ onTaskAction: () => {},
+ task,
+ taskActionKey: 'task-1:pause',
+ t,
+ }),
+ );
+
+ expect(html).toContain('task-actions inbox-actions');
+ expect(html).toContain('task-action task-action-pause is-busy');
+ expect(html).toContain('aria-busy="true"');
+ expect(html).toContain(t.tasks.actions.busy);
+ expect(html).toContain(t.tasks.actions.cancel);
+ });
+});
diff --git a/apps/dashboard/src/TaskActionButtons.tsx b/apps/dashboard/src/TaskActionButtons.tsx
new file mode 100644
index 0000000..95b5d67
--- /dev/null
+++ b/apps/dashboard/src/TaskActionButtons.tsx
@@ -0,0 +1,52 @@
+import type { DashboardTask, DashboardTaskAction } from './api';
+import { taskActionsFor } from './dashboardHelpers';
+import type { Messages } from './i18n';
+
+export type TaskActionKey =
+ | 'create'
+ | `${string}:edit`
+ | `${string}:${DashboardTaskAction}`;
+
+interface TaskActionButtonsProps {
+ className?: string;
+ onTaskAction: (task: DashboardTask, action: DashboardTaskAction) => void;
+ task: DashboardTask;
+ taskActionKey: string | null;
+ t: Messages;
+}
+
+export function TaskActionButtons({
+ className,
+ onTaskAction,
+ task,
+ taskActionKey,
+ t,
+}: TaskActionButtonsProps) {
+ const taskActions = taskActionsFor(task);
+ if (taskActions.length === 0) return null;
+
+ const rootClassName = className
+ ? `task-actions ${className}`
+ : 'task-actions';
+
+ return (
+
+ {taskActions.map((action) => {
+ const actionKey: TaskActionKey = `${task.id}:${action}`;
+ const busy = taskActionKey === actionKey;
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/apps/dashboard/src/TaskPanel.tsx b/apps/dashboard/src/TaskPanel.tsx
index 4cd32f3..c197c48 100644
--- a/apps/dashboard/src/TaskPanel.tsx
+++ b/apps/dashboard/src/TaskPanel.tsx
@@ -11,16 +11,14 @@ import type {
import { EmptyState } from './EmptyState';
import { localeTags, type Locale, type Messages } from './i18n';
import { redactSecretsForPreview } from './redaction';
-import { statusLabel, taskActionsFor } from './dashboardHelpers';
+import { statusLabel } from './dashboardHelpers';
+import { TaskActionButtons, type TaskActionKey } from './TaskActionButtons';
+
+export type { TaskActionKey } from './TaskActionButtons';
type TaskGroupKey = 'watchers' | 'scheduled' | 'paused' | 'completed';
type TaskResultTone = 'ok' | 'fail' | 'none';
-export type TaskActionKey =
- | 'create'
- | `${string}:edit`
- | `${string}:${DashboardTaskAction}`;
-
export interface RoomOption {
jid: string;
name: string;
@@ -69,13 +67,6 @@ interface TaskCardProps {
t: Messages;
}
-interface TaskActionButtonsProps {
- onTaskAction: (task: DashboardTask, action: DashboardTaskAction) => void;
- task: DashboardTask;
- taskActionKey: TaskActionKey | null;
- t: Messages;
-}
-
interface TaskDateProps {
locale: Locale;
t: Messages;
@@ -341,37 +332,6 @@ function TaskCreateForm({
);
}
-function TaskActionButtons({
- onTaskAction,
- task,
- taskActionKey,
- t,
-}: TaskActionButtonsProps) {
- const taskActions = taskActionsFor(task);
- if (taskActions.length === 0) return null;
-
- return (
-
- {taskActions.map((action) => {
- const actionKey: TaskActionKey = `${task.id}:${action}`;
- const busy = taskActionKey === actionKey;
- return (
-
- );
- })}
-
- );
-}
-
function TaskTimeGrid({ locale, t, task }: TaskDateProps) {
return (