Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/admin/pages/site/toolbar/PublishButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,37 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
return () => { cancelled = true }
}, [enabled, siteId])

// Re-confirm publish availability whenever a draft save completes. The
// `hasUnsavedChanges` reset effect below catches manual edits (the flag stays
// true long enough to be observed), but an MCP editor-bridge edit flushes the
// save immediately (`flushEditorSave`), flipping the flag true→false within a
// single tick — too fast for that effect to ever render with `true`, so the
// button stayed stuck on "Published". Keying off the save's `lastSavedAt`
// re-asks the server after every save (manual OR bridge), so bridge-driven
// edits surface the Publish affordance without an editor reload.
const lastSavedAt = saveStatus?.state === 'saved' ? saveStatus.lastSavedAt : undefined
useEffect(() => {
if (!enabled || !siteId || lastSavedAt === undefined) return
let cancelled = false

async function refreshAfterSave() {
try {
const status = await getCmsPublishStatus()
if (cancelled) return
setState((prev) => {
if (prev === 'publishing') return prev // never interrupt an in-flight publish
if (status.draftMatchesPublished) return 'published'
return prev === 'published' ? 'idle' : prev // draft now differs → re-enable Publish
})
} catch (err) {
console.warn('[toolbar] Failed to refresh publish status:', err)
}
}

void refreshAfterSave()
return () => { cancelled = true }
}, [enabled, siteId, lastSavedAt])

useEffect(() => {
if (!hasUnsavedChanges || state !== 'published') return
if (statusTimerRef.current) clearTimeout(statusTimerRef.current)
Expand Down