feat(web): Integrate M4-LLM error handling improvements
Port high-value features from work/m4-llm branch into develop's security-hardened codebase: - Separate LLM vs persistence error handling in useChat (shows assistant response even when save fails) - Add structured error context logging with errorType, messagePreview, messageCount fields for debugging - Enforce state invariant in useChatOverlay: cannot be minimized when closed - Add onStorageError callback with user-friendly messages and per-error-type deduplication - Add error logging to Chat imperative handle methods - Create Chat.test.tsx with loadConversation failure mode tests Skipped from work/m4-llm (superseded by develop): - AbortSignal timeout (develop has centralized client timeout) - Custom toast system (duplicates @mosaic/ui) - ErrorBoundary (develop has its own) - WebSocket typed events (develop's ref-based pattern is superior) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -170,7 +170,12 @@ describe("isMessageArray", () => {
|
||||
describe("isChatOverlayState", () => {
|
||||
it("should return true for a valid ChatOverlayState", () => {
|
||||
expect(isChatOverlayState({ isOpen: true, isMinimized: false })).toBe(true);
|
||||
expect(isChatOverlayState({ isOpen: false, isMinimized: true })).toBe(true);
|
||||
expect(isChatOverlayState({ isOpen: true, isMinimized: true })).toBe(true);
|
||||
expect(isChatOverlayState({ isOpen: false, isMinimized: false })).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject invalid state: closed AND minimized (invariant violation)", () => {
|
||||
expect(isChatOverlayState({ isOpen: false, isMinimized: true })).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for non-object values", () => {
|
||||
|
||||
@@ -76,12 +76,16 @@ export function isMessageArray(value: unknown): value is {
|
||||
/**
|
||||
* Type guard: validates ChatOverlayState shape
|
||||
* Expects { isOpen: boolean, isMinimized: boolean }
|
||||
* Enforces invariant: cannot be minimized when closed
|
||||
*/
|
||||
export function isChatOverlayState(
|
||||
value: unknown
|
||||
): value is { isOpen: boolean; isMinimized: boolean } {
|
||||
if (!isRecord(value)) return false;
|
||||
return typeof value.isOpen === "boolean" && typeof value.isMinimized === "boolean";
|
||||
if (typeof value.isOpen !== "boolean" || typeof value.isMinimized !== "boolean") return false;
|
||||
// Invariant: cannot be minimized when closed
|
||||
if (!value.isOpen && value.isMinimized) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user