diff --git a/.gitea/workflows/e2e-chat.yml b/.gitea/workflows/e2e-chat.yml index 35d5c2048..b8d3ca6a2 100644 --- a/.gitea/workflows/e2e-chat.yml +++ b/.gitea/workflows/e2e-chat.yml @@ -97,7 +97,7 @@ jobs: cache-dependency-path: workspace-server/go.sum - if: needs.detect-changes.outputs.chat == 'true' - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d6f5 # v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '22' cache: 'npm' diff --git a/canvas/src/components/BroadcastBanner.tsx b/canvas/src/components/BroadcastBanner.tsx new file mode 100644 index 000000000..28a9f5cac --- /dev/null +++ b/canvas/src/components/BroadcastBanner.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useCallback } from "react"; +import { useCanvasStore } from "@/store/canvas"; + +/** Org-wide broadcast banner. + * + * Rendered at the top of the canvas (below the toolbar) whenever the store + * holds one or more unread BROADCAST_MESSAGE entries. Each entry shows: + * - sender name (workspace that issued the broadcast) + * - the message text + * - a dismiss button + * + * Dismissing an entry removes it from the store via consumeBroadcastMessages. + * The dismissed state is intentionally ephemeral — dismissed broadcasts reappear + * on page refresh since they are not persisted server-side; this is intentional + * (the platform's activity log already provides the audit trail). + */ +export function BroadcastBanner() { + const broadcastMessages = useCanvasStore((s) => s.broadcastMessages); + const consumeBroadcastMessages = useCanvasStore((s) => s.consumeBroadcastMessages); + + const handleDismiss = useCallback(() => { + void consumeBroadcastMessages(); + }, [consumeBroadcastMessages]); + + if (broadcastMessages.length === 0) return null; + + return ( +
- Failed to load chat history: {history.loadError} + Failed to load chat history: {loadError}