From d460e8ac33d62ac2a12bb1fe9a9a676caea1bf66 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 15 Jul 2026 21:52:11 -0700 Subject: [PATCH 1/2] topbar: surface publish warnings instead of discarding them handlePublish's JSON response has always included a `warnings` array (e.g. the contact-form relay's "submissions will not be delivered" notice), but nothing in the editor ever read it. Adds a PublishWarnings banner, held in its own state independent of the 3s publishStatus flash so the customer has time to read it, rendered in both the desktop and mobile TopBar branches. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../panels/topbar/PublishWarnings.test.tsx | 57 +++++++++++++++++++ craft/src/panels/topbar/PublishWarnings.tsx | 31 ++++++++++ craft/src/panels/topbar/TopBar.tsx | 9 +++ craft/src/styles/editor.css | 35 ++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 craft/src/panels/topbar/PublishWarnings.test.tsx create mode 100644 craft/src/panels/topbar/PublishWarnings.tsx diff --git a/craft/src/panels/topbar/PublishWarnings.test.tsx b/craft/src/panels/topbar/PublishWarnings.test.tsx new file mode 100644 index 0000000..7fd309d --- /dev/null +++ b/craft/src/panels/topbar/PublishWarnings.test.tsx @@ -0,0 +1,57 @@ +import { describe, test, expect, vi, afterEach } from 'vitest'; +import React from 'react'; +import { createRoot, Root } from 'react-dom/client'; +import { act } from 'react-dom/test-utils'; +import { PublishWarnings } from './PublishWarnings'; + +/* ---------- DOM test harness (no @testing-library/react in this repo, see + src/ui/AssetPicker.test.tsx for the same pattern: react-dom/client + + react-dom/test-utils `act`, both transitive deps of react-dom already). ---------- */ +let container: HTMLDivElement; +let root: Root; + +function render(ui: React.ReactElement) { + container = document.createElement('div'); + document.body.appendChild(container); + act(() => { + root = createRoot(container); + root.render(ui); + }); +} + +function click(el: Element | null) { + if (!el) throw new Error('element not found'); + act(() => { (el as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true })); }); +} + +afterEach(() => { + if (container) { + act(() => { root.unmount(); }); + container.remove(); + } +}); + +describe('PublishWarnings', () => { + test('renders nothing when there are no warnings', () => { + render( {}} />); + expect(container.innerHTML).toBe(''); + }); + + test('renders each warning', () => { + render( + {}} + />, + ); + expect(container.textContent).toContain('First problem.'); + expect(container.textContent).toContain('Second problem.'); + }); + + test('dismiss fires the callback', () => { + const onDismiss = vi.fn(); + render(); + click(container.querySelector('[data-testid="publish-warnings-dismiss"]')); + expect(onDismiss).toHaveBeenCalledTimes(1); + }); +}); diff --git a/craft/src/panels/topbar/PublishWarnings.tsx b/craft/src/panels/topbar/PublishWarnings.tsx new file mode 100644 index 0000000..3a0a81e --- /dev/null +++ b/craft/src/panels/topbar/PublishWarnings.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +export interface PublishWarningsProps { + warnings: string[]; + onDismiss: () => void; +} + +/** Non-blocking banner shown after a successful publish. The site IS live -- + * these are things the customer should fix and re-publish, not failures. */ +export const PublishWarnings: React.FC = ({ warnings, onDismiss }) => { + if (!warnings.length) return null; + + return ( +
+