diff --git a/src/apps/officer-web/Screens/Dashboard/Jobs/JobsPage.tsx b/src/apps/officer-web/Screens/Dashboard/Jobs/JobsPage.tsx
index af95e1a8..6cb9202f 100644
--- a/src/apps/officer-web/Screens/Dashboard/Jobs/JobsPage.tsx
+++ b/src/apps/officer-web/Screens/Dashboard/Jobs/JobsPage.tsx
@@ -11,10 +11,12 @@ import {
Inbox,
Square,
Trash2,
+ Bell,
} from 'lucide-react';
import { WorkspaceLayout } from 'officerdev';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { useClient } from 'hooks/useClient';
+import { toast } from 'sonner';
import { useDashboardState } from 'state/useDashboardState';
import { Card } from '@/components/Card';
import { ScriptJobDetail } from './ScriptJobDetail';
@@ -138,6 +140,47 @@ const PanelHeader = ({ children }: { children: ReactNode }) => (
);
// Top-left panel: running first, then the FIFO queue (oldest pending on top — next to run).
+// TEMPORARY. The first producer for officer-notify, so the pipe can be exercised from the UI before any
+// real event is wired to it. Sends { type: 'test' }, which reaches every configured channel — Discord
+// today, APNs and FCM once their credentials exist. Delete this once a real producer replaces it.
+const TestNotificationButton = () => {
+ const client = useClient();
+ const [sending, setSending] = useState(false);
+
+ const send = async () => {
+ setSending(true);
+ try {
+ // userId is omitted deliberately: the sidecar takes it from the header the proxy injects.
+ const res = await client.post<{ results?: { channel: string; sent: number; failed: number }[] }>(
+ '/notify/_officer/notify',
+ { type: 'test' },
+ );
+ const results = res?.results ?? [];
+ if (results.length === 0) {
+ toast.warning('No channels configured');
+ } else {
+ toast.success(results.map((r) => `${r.channel}: ${r.sent} sent`).join(', '));
+ }
+ } catch {
+ toast.error('Failed to send notification');
+ } finally {
+ setSending(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
const ActiveJobsPanel = () => {
const { jobs, isLoading, act } = useJobsData();
const active = [
@@ -150,6 +193,7 @@ const ActiveJobsPanel = () => {
Running & Queued
{active.length > 0 && {active.length}}
+