## Summary Restores green **Full Suite (non-blocking)** runs on `main`. Recent main merges left i18n key parity, schema baseline bookkeeping (0011→0012), heartbeat tool inventory (FN-8058 `fn_task_logs_read`), and merger whitespace-classification mocks (execFile `git diff -p -w :2: :3:`) out of date, so all four test shards failed. ## Root causes observed on main - **Shard 4 / `@fusion/i18n`**: missing `skipConfirmationDialogs*` + `reviewBudgetExhausted` in non-en locales; orphan `awaitingApprovalPlanReviewReplanCap` - **Shard 3 / `@fusion/core`**: `SCHEMA_BASELINE_VERSION` advanced to `0012` while tests still equated it with `OWNER_PROJECT_ID_SPLIT_VERSION` (`0011`) and omitted `0012` from applied-migration lists - **Shards 1–2 / `@fusion/engine`**: tool count/snapshot drift for `fn_task_logs_read`; merger tests still mocked `git diff-tree` for trivial classification after the execFile `:2:`/`:3:` cutover; mock provider `updateTask` arity drift ## Changes - Locale catalogs: add missing keys, drop orphan key - Schema applier tests: immutable 0011 identity + baseline 0012 lists - Heartbeat + gating snapshots: include `fn_task_logs_read` - Merger unit mocks: recognize `git diff -p -w :2:path :3:path` - Mock provider: accept optional third `updateTask` arg ## Test plan - [x] `pnpm --filter @fusion/i18n exec vitest run` — 23/23 - [x] `pnpm --filter @fusion/core exec vitest run src/__tests__/postgres/schema-applier.test.ts` (immutable + automation upgrade) — pass - [x] `pnpm --filter @fusion/core exec vitest run` project-identity + satellite-fusiondir — pass - [x] Engine suites from failed CI shards (file-scoped, hermes/openclaw/paperclip/grok, reliability post-finalize/mission, heartbeat, gating, merger recovery/prompt, mock-provider, etc.) — pass - [ ] Full Suite workflow green on merge to main <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Improved project data isolation across backend operations. - Added safer optional toast handling when UI components render outside the full application shell. - Added support for reading task logs during agent heartbeat sessions. - **Bug Fixes** - Prevented runtime probes from hanging and avoided scanning large binary files. - Improved path handling for workspaces with missing descendants. - Corrected task retry state resets and GitHub import/issue-close behavior. - **Style** - Improved chat, terminal, and settings spacing. - Added clearer accessibility labeling for the auto-merge control. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import { useState, useCallback, useRef, createContext, useContext, useEffect } from "react";
|
|
import type { ReactNode } from "react";
|
|
import React from "react";
|
|
|
|
export type ToastType = "success" | "error" | "info" | "warning";
|
|
|
|
export interface Toast {
|
|
id: number;
|
|
message: string;
|
|
type: ToastType;
|
|
}
|
|
|
|
interface ToastContextValue {
|
|
toasts: Toast[];
|
|
addToast: (message: string, type?: ToastType) => void;
|
|
removeToast: (id: number) => void;
|
|
}
|
|
|
|
const ToastContext = createContext<ToastContextValue | null>(null);
|
|
|
|
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
const nextId = useRef(0);
|
|
const timers = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());
|
|
|
|
const removeToast = useCallback((id: number) => {
|
|
const timer = timers.current.get(id);
|
|
if (timer !== undefined) {
|
|
clearTimeout(timer);
|
|
timers.current.delete(id);
|
|
}
|
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
}, []);
|
|
|
|
const addToast = useCallback((message: string, type: ToastType = "info") => {
|
|
// Suppress bare network-failure toasts (TypeError "Failed to fetch" from
|
|
// aborted in-flight requests when the tab is backgrounded/resumed on
|
|
// mobile). Toasts with additional context still pass through.
|
|
if (type === "error" && /^\s*Failed to fetch\.?\s*$/i.test(message)) {
|
|
return;
|
|
}
|
|
const id = nextId.current++;
|
|
setToasts((prev) => [...prev, { id, message, type }]);
|
|
const timer = setTimeout(() => removeToast(id), 4000);
|
|
timers.current.set(id, timer);
|
|
}, [removeToast]);
|
|
|
|
// Clear all pending timers on unmount to prevent state updates on unmounted components
|
|
useEffect(() => {
|
|
return () => {
|
|
timers.current.forEach((timer) => clearTimeout(timer));
|
|
timers.current.clear();
|
|
};
|
|
}, []);
|
|
|
|
return React.createElement(ToastContext.Provider, { value: { toasts, addToast, removeToast } }, children);
|
|
}
|
|
|
|
export function useToast(): ToastContextValue {
|
|
const ctx = useContext(ToastContext);
|
|
if (!ctx) throw new Error("useToast must be used within ToastProvider");
|
|
return ctx;
|
|
}
|
|
|
|
/*
|
|
FNXC:ToastProvider 2026-07-14-19:25:
|
|
Board-card decorations such as RuntimeFallbackBadge may mount outside a full app shell (unit/harness tests and optional plugin surfaces). Optional toast access lets those leaves skip toasting instead of throwing and blanking the board via ErrorBoundary.
|
|
*/
|
|
export function useOptionalToast(): ToastContextValue | null {
|
|
return useContext(ToastContext);
|
|
}
|