fix: session persistence + h3 readability + outline error messages

Session issues fixed:
- Removed auto-draft-only gate for showing unassigned sessions on new posts
  (now shows on any post that has no linked sessions)
- Auto-link unassigned session to current post when user opens it
- Added beforeunload/pagehide flush to persist messages before page close
  (prevents data loss from 700ms debounce not firing)
- Added warning log when session loads with 0 messages for debugging

UI fix:
- Override WP editor h3 shrinkage (11px/uppercase → 15px/normal/white)
- Fix h2/h4-h6 headings in response content for dark theme readability

Outline error messages:
- Separate empty response from parse failure with distinct actionable messages
- Show model name + token count on empty response for debugging
- Reassure user that parse failures are usually one-time issues
This commit is contained in:
Dwindi Ramadhana
2026-06-06 00:58:08 +07:00
parent f7bf1f5153
commit b4ea9025b1
3 changed files with 118 additions and 7 deletions

View File

@@ -1078,6 +1078,39 @@
}
}, [sanitizeMessagesForStorage]);
// Flush pending message persistence on page unload to prevent data loss
React.useEffect(() => {
const flushOnUnload = () => {
if (messagesSaveTimeoutRef.current) {
clearTimeout(messagesSaveTimeoutRef.current);
}
if (!currentSessionId || isHydratingSessionRef.current) {
return;
}
const sanitized = sanitizeMessagesForStorage(messages);
const serialized = JSON.stringify(sanitized);
if (serialized === lastPersistedMessagesRef.current) {
return;
}
// Synchronous XHR as last resort during unload (sendBeacon can't set headers)
try {
const xhr = new XMLHttpRequest();
xhr.open('POST', `${wpAgenticWriter.apiUrl}/conversations/${currentSessionId}/messages`, false); // sync
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('X-WP-Nonce', wpAgenticWriter.nonce);
xhr.send(JSON.stringify({ messages: sanitized }));
} catch (e) {
// Best effort - ignore errors during unload
}
};
window.addEventListener('beforeunload', flushOnUnload);
window.addEventListener('pagehide', flushOnUnload);
return () => {
window.removeEventListener('beforeunload', flushOnUnload);
window.removeEventListener('pagehide', flushOnUnload);
};
}, [currentSessionId, messages, sanitizeMessagesForStorage]);
React.useEffect(() => {
if (!currentSessionId) {
return;
@@ -1205,10 +1238,10 @@
postSessions = Array.isArray(postData?.sessions) ? postData.sessions : [];
}
// Auto-draft fallback:
// if this auto-draft has no linked sessions yet, surface active carry-over sessions
// (unassigned or still auto-draft) so users can continue unfinished work.
if (postSessions.length === 0 && currentPostStatus === 'auto-draft') {
// Fallback: if this post has no linked sessions, surface active unassigned sessions
// so users can continue unfinished work from other tabs or prior page loads.
// This covers auto-draft AND draft posts that haven't had a session linked yet.
if (postSessions.length === 0) {
const activeRes = await fetch(`${wpAgenticWriter.apiUrl}/conversations?status=active&limit=50`, {
method: 'GET',
headers,
@@ -1219,7 +1252,8 @@
unassignedSessions = allActive.filter((s) => {
const pid = Number(s?.post_id || 0);
const postStatus = String(s?.post_status || '').toLowerCase();
return pid === 0 || postStatus === 'auto-draft';
// Show sessions that are unassigned, or linked to auto-drafts/drafts
return pid === 0 || postStatus === 'auto-draft' || postStatus === '';
});
}
}
@@ -1281,10 +1315,30 @@
isHydratingSessionRef.current = true;
setCurrentSessionId(sessionId);
const sessionMessages = Array.isArray(data?.messages) ? data.messages : [];
// If session has no messages but has a post_id, it may have been improperly persisted
if (sessionMessages.length === 0) {
wpawLog.warn('Session loaded with 0 messages:', sessionId);
}
lastPersistedMessagesRef.current = JSON.stringify(sanitizeMessagesForStorage(sessionMessages));
hydrateSessionStateFromMessages(sessionMessages);
setMessages(sessionMessages);
setShowWelcome(false);
// Auto-link unassigned session to current post for continuity
const sessionPostId = Number(data?.post_id || 0);
if (postId && postId > 0 && sessionPostId === 0) {
fetch(`${wpAgenticWriter.apiUrl}/conversations/${sessionId}/link-post`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpAgenticWriter.nonce,
},
body: JSON.stringify({ postId: postId }),
}).catch(() => {}); // Non-blocking
}
setTimeout(() => {
isHydratingSessionRef.current = false;
}, 0);