Surface publish warnings in the editor instead of discarding them #26

Merged
jknapp merged 2 commits from feat/publish-warnings into main 2026-07-16 12:34:33 +00:00
Owner

Companion to whp PR #48 (feat/publish-broken-asset-check). Ship together — the backend emits the warnings, this renders them.

The bug this fixes

handlePublish has always returned a warnings array in its publish response. Nothing in the editor has ever read it — TopBar.tsx checked only result.success and flashed "Published" for 3 seconds.

So the contact-form relay warning — "submissions will not be delivered until an administrator enables it" — has been dead code for its entire life. No customer has ever seen it. A customer whose contact form silently went nowhere was never told.

This makes warnings visible, which revives that warning for free and carries the new broken-asset warnings from the companion PR.

What's here

  • PublishWarnings.tsx — a non-blocking, dismissible banner. Renders nothing when there are no warnings (the common, healthy-site case).
  • TopBar.tsx — holds warnings in their own state and renders the banner in both the mobile and desktop branches (TopBar early-returns for mobile, so a single render site would silently not exist on mobile).

Details worth knowing

  • Non-blocking. The site did publish. role="status" (not alert), amber rather than the error red, dismissible.
  • Warnings survive the 3s "Published" flash. They're held separately from publishStatus precisely so the existing setTimeout(…, 3000) doesn't take them down before the customer can read them.
  • Backend strings render as-is via a plain JSX text node ({w}), so React escapes them — the strings interpolate customer-controlled page names, and this is what stops a page titled with markup from self-injecting. No dangerouslySetInnerHTML.
  • .topbar gained position: relative — the banner's position: absolute; top: 100% needs a positioned ancestor or it renders off-screen. Verified zero blast radius: no other absolutely-positioned descendant lives under .topbar, the modals portal to body, and the mobile overflow menu is position: fixed.

Testing

918 tests / 90 files passing, tsc clean.

TopBar.test.tsx covers the wiring seam specifically — and that matters here more than usual, because the wiring was the bug: the relay warning died precisely because TopBar discarded result.warnings. The test was confirmed to fail against deliberately-broken wiring:

  • reverting to discarding result.warnings (the original bug) → 4/5 tests fail
  • re-coupling the 3s timer to clear warnings → only the "survives the flash" test fails, isolating that regression class

Tests use the repo's existing react-dom/client + act() harness (@testing-library/react isn't installed here).

🤖 Generated with Claude Code

Companion to **whp PR #48** (`feat/publish-broken-asset-check`). Ship together — the backend emits the warnings, this renders them. ## The bug this fixes `handlePublish` has **always** returned a `warnings` array in its publish response. Nothing in the editor has ever read it — `TopBar.tsx` checked only `result.success` and flashed "Published" for 3 seconds. So the contact-form relay warning — *"submissions will not be delivered until an administrator enables it"* — has been **dead code for its entire life**. No customer has ever seen it. A customer whose contact form silently went nowhere was never told. This makes warnings visible, which revives that warning for free and carries the new broken-asset warnings from the companion PR. ## What's here - **`PublishWarnings.tsx`** — a non-blocking, dismissible banner. Renders nothing when there are no warnings (the common, healthy-site case). - **`TopBar.tsx`** — holds warnings in their own state and renders the banner in **both** the mobile and desktop branches (TopBar early-returns for mobile, so a single render site would silently not exist on mobile). ## Details worth knowing - **Non-blocking.** The site *did* publish. `role="status"` (not `alert`), amber rather than the error red, dismissible. - **Warnings survive the 3s "Published" flash.** They're held separately from `publishStatus` precisely so the existing `setTimeout(…, 3000)` doesn't take them down before the customer can read them. - **Backend strings render as-is** via a plain JSX text node (`{w}`), so React escapes them — the strings interpolate customer-controlled page names, and this is what stops a page titled with markup from self-injecting. No `dangerouslySetInnerHTML`. - `.topbar` gained `position: relative` — the banner's `position: absolute; top: 100%` needs a positioned ancestor or it renders off-screen. Verified zero blast radius: no other absolutely-positioned descendant lives under `.topbar`, the modals portal to body, and the mobile overflow menu is `position: fixed`. ## Testing **918 tests / 90 files passing**, `tsc` clean. `TopBar.test.tsx` covers the wiring seam specifically — and that matters here more than usual, because **the wiring *was* the bug**: the relay warning died precisely because TopBar discarded `result.warnings`. The test was confirmed to fail against deliberately-broken wiring: - reverting to discarding `result.warnings` (the original bug) → **4/5 tests fail** - re-coupling the 3s timer to clear warnings → **only** the "survives the flash" test fails, isolating that regression class Tests use the repo's existing `react-dom/client` + `act()` harness (`@testing-library/react` isn't installed here). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 2 commits 2026-07-16 05:14:44 +00:00
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) <noreply@anthropic.com>
PublishWarnings.tsx had unit tests for the presentational banner, but
nothing asserted that result.warnings from publish() actually flows
through TopBar's handlePublish into it. That 3-line seam is exactly what
this feature exists to fix -- the backend always returned warnings, and
TopBar discarded them by only checking result.success, so the
contact-form-relay warning was dead code for its entire life.

Adds TopBar.test.tsx asserting: warnings render after a successful
publish with warnings, no banner renders when warnings is absent, a
warning doesn't present as a publish failure, warnings survive the 3s
"Published" flash (fake timers, advanced past 3000ms), and a fresh
publish clears stale warnings from the previous one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jknapp merged commit f5d2a23a5f into main 2026-07-16 12:34:33 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cloud-hosting-platform/site-builder#26