FN-7156: open Missions on overview by default
Ensure the Missions tab opens to the overview unless an explicit mission target is requested. - Remove selected-mission cache restore and inline first-mission auto-selection. - Treat hidden inline Missions as inactive so reopening resets to overview. - Update SWR cache keys and dashboard tests for the overview-first behavior. - Add a patch changeset documenting the operator-facing fix. Files changed: .changeset/fn-7156-missions-overview-default.md | 7 + .../dashboard/app/components/DashboardLoader.tsx | 1 - .../dashboard/app/components/MissionManager.tsx | 50 ++---- .../components/__tests__/DashboardLoader.test.tsx | 1 - .../MissionManager.overview-default.test.tsx | 174 +++++++++++++++++++++ .../dashboard/app/utils/__tests__/swrCache.test.ts | 1 - packages/dashboard/app/utils/swrCache.ts | 1 - 7 files changed, 191 insertions(+), 44 deletions(-) Fusion-Task-Id: FN-7156 Fusion-Task-Lineage: 1be987d5-33c6-4a59-905f-14c8d22b75d1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7156-missions-overview-default.md
Normal file
7
.changeset/fn-7156-missions-overview-default.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Missions tab now always opens the mission overview instead of a specific mission.
|
||||||
|
category: fix
|
||||||
|
dev: Removed MissionManager cache-restore and default-select effects; targetMissionId deep-links and interview resume still open a specific mission.
|
||||||
@@ -67,7 +67,6 @@ export function DashboardLoader({ stage }: DashboardLoaderProps) {
|
|||||||
clearCache(SWR_CACHE_KEYS.EVALS_RUNS_PREFIX);
|
clearCache(SWR_CACHE_KEYS.EVALS_RUNS_PREFIX);
|
||||||
clearCache(SWR_CACHE_KEYS.EVALS_RESULTS_PREFIX);
|
clearCache(SWR_CACHE_KEYS.EVALS_RESULTS_PREFIX);
|
||||||
clearCache(SWR_CACHE_KEYS.MISSIONS_PREFIX);
|
clearCache(SWR_CACHE_KEYS.MISSIONS_PREFIX);
|
||||||
clearCache(SWR_CACHE_KEYS.MISSIONS_SELECTED_ID_PREFIX);
|
|
||||||
clearCache(SWR_CACHE_KEYS.MAILBOX_INBOX_PREFIX);
|
clearCache(SWR_CACHE_KEYS.MAILBOX_INBOX_PREFIX);
|
||||||
clearCache(SWR_CACHE_KEYS.MAILBOX_OUTBOX_PREFIX);
|
clearCache(SWR_CACHE_KEYS.MAILBOX_OUTBOX_PREFIX);
|
||||||
clearCache(SWR_CACHE_KEYS.MAILBOX_UNREAD_COUNT_PREFIX);
|
clearCache(SWR_CACHE_KEYS.MAILBOX_UNREAD_COUNT_PREFIX);
|
||||||
|
|||||||
@@ -619,10 +619,13 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
|||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
const sessionTabId = useMemo(() => getSessionTabId(), []);
|
const sessionTabId = useMemo(() => getSessionTabId(), []);
|
||||||
const isActive = isInline || isOpen;
|
/*
|
||||||
|
FNXC:Missions 2026-06-27-20:25:
|
||||||
|
Inline Missions can stay mounted while hidden, so tab visibility must drive active state. Re-opening the tab must reset to overview instead of preserving the previously selected mission.
|
||||||
|
*/
|
||||||
|
const isActive = isOpen;
|
||||||
const cacheSuffix = projectId ?? "";
|
const cacheSuffix = projectId ?? "";
|
||||||
const missionsCacheKey = `${SWR_CACHE_KEYS.MISSIONS_PREFIX}${cacheSuffix}`;
|
const missionsCacheKey = `${SWR_CACHE_KEYS.MISSIONS_PREFIX}${cacheSuffix}`;
|
||||||
const selectedMissionIdCacheKey = `${SWR_CACHE_KEYS.MISSIONS_SELECTED_ID_PREFIX}${cacheSuffix}`;
|
|
||||||
const initialMissions = readCache<MissionWithSummary[]>(missionsCacheKey);
|
const initialMissions = readCache<MissionWithSummary[]>(missionsCacheKey);
|
||||||
const [missions, setMissions] = useState<MissionWithSummary[]>(() => (Array.isArray(initialMissions) ? initialMissions : []));
|
const [missions, setMissions] = useState<MissionWithSummary[]>(() => (Array.isArray(initialMissions) ? initialMissions : []));
|
||||||
const [selectedMission, setSelectedMission] = useState<MissionWithHierarchy | null>(null);
|
const [selectedMission, setSelectedMission] = useState<MissionWithHierarchy | null>(null);
|
||||||
@@ -1252,23 +1255,6 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
|||||||
hasHydratedRef.current = hasCachedMissions;
|
hasHydratedRef.current = hasCachedMissions;
|
||||||
}, [missionsCacheKey]);
|
}, [missionsCacheKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const selectedMissionId = readCache<string | null>(selectedMissionIdCacheKey);
|
|
||||||
if (!selectedMissionId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const hasMissionSummary = missions.some((mission) => mission.id === selectedMissionId);
|
|
||||||
if (hasMissionSummary && !selectedMission) {
|
|
||||||
void loadMissionDetail(selectedMissionId);
|
|
||||||
}
|
|
||||||
}, [loadMissionDetail, missions, selectedMission, selectedMissionIdCacheKey]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedMission?.id) {
|
|
||||||
writeCache(selectedMissionIdCacheKey, selectedMission.id, { maxBytes: 500_000 });
|
|
||||||
}
|
|
||||||
}, [selectedMission?.id, selectedMissionIdCacheKey]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
loadMissions();
|
loadMissions();
|
||||||
@@ -1284,6 +1270,10 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
|||||||
}
|
}
|
||||||
}, [isActive, loadActiveGoals, loadMissions]);
|
}, [isActive, loadActiveGoals, loadMissions]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:Missions 2026-06-27-00:00:
|
||||||
|
Entering the Missions tab must always land on the overview list with an empty detail pane. Only explicit `targetMissionId` deep links or interview resume sessions may open a specific mission automatically; do not restore the last selected mission or default-select the first mission on tab entry.
|
||||||
|
*/
|
||||||
// Auto-load target mission when specified
|
// Auto-load target mission when specified
|
||||||
const targetLoadedRef = useRef<string | null>(null);
|
const targetLoadedRef = useRef<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1300,25 +1290,6 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
|||||||
}
|
}
|
||||||
}, [isActive]);
|
}, [isActive]);
|
||||||
|
|
||||||
// Default-select the first mission once the list loads (inline desktop view).
|
|
||||||
// Gated on `isInline` so the standalone modal flow (and unit tests that
|
|
||||||
// render without isInline) keep the explicit "select a mission" empty state.
|
|
||||||
const defaultSelectedRef = useRef(false);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isActive || !isInline || isMobile || loading) return;
|
|
||||||
if (defaultSelectedRef.current) return;
|
|
||||||
if (selectedMission || targetMissionId) return;
|
|
||||||
if (missions.length === 0) return;
|
|
||||||
defaultSelectedRef.current = true;
|
|
||||||
loadMissionDetail(missions[0].id);
|
|
||||||
}, [isActive, isInline, isMobile, loading, missions, selectedMission, targetMissionId, loadMissionDetail]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isActive) {
|
|
||||||
defaultSelectedRef.current = false;
|
|
||||||
}
|
|
||||||
}, [isActive]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isActive || !selectedMission || activeTab !== "activity") {
|
if (!isActive || !selectedMission || activeTab !== "activity") {
|
||||||
return;
|
return;
|
||||||
@@ -2470,7 +2441,6 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
|||||||
}, [loadMissionDetail]);
|
}, [loadMissionDetail]);
|
||||||
|
|
||||||
const handleBackToList = useCallback(() => {
|
const handleBackToList = useCallback(() => {
|
||||||
writeCache(selectedMissionIdCacheKey, null, { maxBytes: 500_000 });
|
|
||||||
setSelectedMission(null);
|
setSelectedMission(null);
|
||||||
setSelectedMilestoneId(null);
|
setSelectedMilestoneId(null);
|
||||||
setValidationTelemetry(null);
|
setValidationTelemetry(null);
|
||||||
@@ -2480,7 +2450,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr
|
|||||||
setEventsFilter("all");
|
setEventsFilter("all");
|
||||||
setExpandedEventMetadata(new Set());
|
setExpandedEventMetadata(new Set());
|
||||||
loadMissions();
|
loadMissions();
|
||||||
}, [loadMissions, selectedMissionIdCacheKey]);
|
}, [loadMissions]);
|
||||||
|
|
||||||
const hasMoreEvents = missionEvents.length < eventsTotal;
|
const hasMoreEvents = missionEvents.length < eventsTotal;
|
||||||
const autopilotState = (selectedMission?.autopilotState ?? "inactive") as AutopilotState;
|
const autopilotState = (selectedMission?.autopilotState ?? "inactive") as AutopilotState;
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ describe("DashboardLoader", () => {
|
|||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.EVALS_RUNS_PREFIX);
|
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.EVALS_RUNS_PREFIX);
|
||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.EVALS_RESULTS_PREFIX);
|
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.EVALS_RESULTS_PREFIX);
|
||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MISSIONS_PREFIX);
|
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MISSIONS_PREFIX);
|
||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MISSIONS_SELECTED_ID_PREFIX);
|
|
||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MAILBOX_INBOX_PREFIX);
|
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MAILBOX_INBOX_PREFIX);
|
||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MAILBOX_OUTBOX_PREFIX);
|
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MAILBOX_OUTBOX_PREFIX);
|
||||||
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MAILBOX_UNREAD_COUNT_PREFIX);
|
expect(mockClearCache).toHaveBeenCalledWith(SWR_CACHE_KEYS.MAILBOX_UNREAD_COUNT_PREFIX);
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
import { MissionManager } from "../MissionManager";
|
||||||
|
|
||||||
|
const mockFetchMissions = vi.fn();
|
||||||
|
const mockFetchMission = vi.fn();
|
||||||
|
const mockFetchMissionsHealth = vi.fn();
|
||||||
|
const mockFetchAiSessions = vi.fn();
|
||||||
|
const mockFetchMissionInterviewDrafts = vi.fn();
|
||||||
|
|
||||||
|
const STALE_SELECTED_MISSION_CACHE_KEY = "kb-dashboard-mission-selected-cache:p1";
|
||||||
|
|
||||||
|
vi.mock("../../hooks/useNavigationHistory", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("../../hooks/useNavigationHistory")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useNavigationHistoryContext: () => ({ pushNav: vi.fn(), replaceCurrent: vi.fn() }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("../../api", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal<typeof import("../../api")>();
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
fetchMissions: (...args: unknown[]) => mockFetchMissions(...args),
|
||||||
|
fetchMission: (...args: unknown[]) => mockFetchMission(...args),
|
||||||
|
fetchMissionsHealth: (...args: unknown[]) => mockFetchMissionsHealth(...args),
|
||||||
|
fetchAiSessions: (...args: unknown[]) => mockFetchAiSessions(...args),
|
||||||
|
fetchMissionInterviewDrafts: (...args: unknown[]) => mockFetchMissionInterviewDrafts(...args),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const missions = [
|
||||||
|
{ id: "M-001", title: "First Mission", description: "", status: "planning", milestones: [] },
|
||||||
|
{ id: "M-002", title: "Deep Linked Mission", description: "", status: "active", milestones: [] },
|
||||||
|
];
|
||||||
|
|
||||||
|
function missionDetail(id: string) {
|
||||||
|
const summary = missions.find((mission) => mission.id === id) ?? missions[0];
|
||||||
|
return {
|
||||||
|
...summary,
|
||||||
|
milestones: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setViewport(width: number) {
|
||||||
|
Object.defineProperty(window, "innerWidth", { value: width, configurable: true });
|
||||||
|
if (!window.matchMedia) {
|
||||||
|
Object.defineProperty(window, "matchMedia", { value: vi.fn(), configurable: true, writable: true });
|
||||||
|
}
|
||||||
|
vi.spyOn(window, "matchMedia").mockImplementation((query: string) => ({
|
||||||
|
matches: query.includes("max-width: 768px") ? width <= 768 : query.includes("min-width: 769px") && query.includes("max-width: 1024px") ? width >= 769 && width <= 1024 : false,
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addListener: vi.fn(),
|
||||||
|
removeListener: vi.fn(),
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn(),
|
||||||
|
dispatchEvent: vi.fn(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMissionManager(props: Partial<ComponentProps<typeof MissionManager>> = {}) {
|
||||||
|
return render(
|
||||||
|
<MissionManager
|
||||||
|
isInline
|
||||||
|
isOpen
|
||||||
|
onClose={() => {}}
|
||||||
|
addToast={() => {}}
|
||||||
|
projectId="p1"
|
||||||
|
{...props}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("MissionManager overview default", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
localStorage.clear();
|
||||||
|
setViewport(1280);
|
||||||
|
mockFetchMissions.mockResolvedValue(missions);
|
||||||
|
mockFetchMission.mockImplementation((missionId: string) => Promise.resolve(missionDetail(missionId)));
|
||||||
|
mockFetchMissionsHealth.mockResolvedValue({});
|
||||||
|
mockFetchAiSessions.mockResolvedValue([]);
|
||||||
|
mockFetchMissionInterviewDrafts.mockResolvedValue([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the desktop overview for a populated inline mission list without auto-loading the first mission", async () => {
|
||||||
|
renderMissionManager();
|
||||||
|
|
||||||
|
expect(await screen.findByText("First Mission")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("mission-empty-detail")).toHaveTextContent("Select a mission to view details");
|
||||||
|
await waitFor(() => expect(mockFetchMission).not.toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets to the overview when an inline tab is hidden and shown after selecting a mission", async () => {
|
||||||
|
const { rerender } = renderMissionManager();
|
||||||
|
|
||||||
|
fireEvent.click(await screen.findByText("First Mission"));
|
||||||
|
await waitFor(() => expect(mockFetchMission).toHaveBeenCalledWith("M-001", "p1"));
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<MissionManager
|
||||||
|
isInline
|
||||||
|
isOpen={false}
|
||||||
|
onClose={() => {}}
|
||||||
|
addToast={() => {}}
|
||||||
|
projectId="p1"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
rerender(
|
||||||
|
<MissionManager
|
||||||
|
isInline
|
||||||
|
isOpen
|
||||||
|
onClose={() => {}}
|
||||||
|
addToast={() => {}}
|
||||||
|
projectId="p1"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.findByText("First Mission")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("mission-empty-detail")).toHaveTextContent("Select a mission to view details");
|
||||||
|
await waitFor(() => expect(mockFetchMission).toHaveBeenCalledTimes(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores a stale selected-mission cache value on desktop and mobile entry", async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
STALE_SELECTED_MISSION_CACHE_KEY,
|
||||||
|
JSON.stringify({ savedAt: Date.now(), data: "M-002" }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { unmount } = renderMissionManager();
|
||||||
|
expect(await screen.findByText("First Mission")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("mission-empty-detail")).toHaveTextContent("Select a mission to view details");
|
||||||
|
await waitFor(() => expect(mockFetchMission).not.toHaveBeenCalled());
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockFetchMissions.mockResolvedValue(missions);
|
||||||
|
mockFetchMission.mockImplementation((missionId: string) => Promise.resolve(missionDetail(missionId)));
|
||||||
|
mockFetchMissionsHealth.mockResolvedValue({});
|
||||||
|
mockFetchAiSessions.mockResolvedValue([]);
|
||||||
|
mockFetchMissionInterviewDrafts.mockResolvedValue([]);
|
||||||
|
setViewport(375);
|
||||||
|
|
||||||
|
renderMissionManager();
|
||||||
|
expect(await screen.findByText("First Mission")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId("mission-empty-detail")).not.toBeInTheDocument();
|
||||||
|
await waitFor(() => expect(mockFetchMission).not.toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("still auto-opens an explicit targetMissionId deep link", async () => {
|
||||||
|
renderMissionManager({ targetMissionId: "M-002" });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetchMission).toHaveBeenCalledWith("M-002", "p1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the overview empty state when there are no missions", async () => {
|
||||||
|
mockFetchMissions.mockResolvedValue([]);
|
||||||
|
|
||||||
|
renderMissionManager();
|
||||||
|
|
||||||
|
expect(await screen.findByText("No missions yet")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("mission-empty-detail")).toHaveTextContent("Select a mission to view details");
|
||||||
|
expect(mockFetchMission).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -119,7 +119,6 @@ describe("swrCache", () => {
|
|||||||
expect(SWR_CACHE_KEYS.EVALS_RUNS_PREFIX).toBe("kb-dashboard-evals-runs-cache:");
|
expect(SWR_CACHE_KEYS.EVALS_RUNS_PREFIX).toBe("kb-dashboard-evals-runs-cache:");
|
||||||
expect(SWR_CACHE_KEYS.EVALS_RESULTS_PREFIX).toBe("kb-dashboard-evals-results-cache:");
|
expect(SWR_CACHE_KEYS.EVALS_RESULTS_PREFIX).toBe("kb-dashboard-evals-results-cache:");
|
||||||
expect(SWR_CACHE_KEYS.MISSIONS_PREFIX).toBe("kb-dashboard-missions-cache:");
|
expect(SWR_CACHE_KEYS.MISSIONS_PREFIX).toBe("kb-dashboard-missions-cache:");
|
||||||
expect(SWR_CACHE_KEYS.MISSIONS_SELECTED_ID_PREFIX).toBe("kb-dashboard-mission-selected-cache:");
|
|
||||||
expect(SWR_CACHE_KEYS.CHAT_SESSIONS_PREFIX).toBe("kb-dashboard-chat-sessions-cache:");
|
expect(SWR_CACHE_KEYS.CHAT_SESSIONS_PREFIX).toBe("kb-dashboard-chat-sessions-cache:");
|
||||||
expect(SWR_CACHE_KEYS.CHAT_MESSAGES_PREFIX).toBe("kb-dashboard-chat-messages-cache:");
|
expect(SWR_CACHE_KEYS.CHAT_MESSAGES_PREFIX).toBe("kb-dashboard-chat-messages-cache:");
|
||||||
expect(SWR_CACHE_KEYS.CHAT_AGENTS_MAP_PREFIX).toBe("kb-dashboard-chat-agents-map-cache:");
|
expect(SWR_CACHE_KEYS.CHAT_AGENTS_MAP_PREFIX).toBe("kb-dashboard-chat-agents-map-cache:");
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export const SWR_CACHE_KEYS = {
|
|||||||
EVALS_RUNS_PREFIX: "kb-dashboard-evals-runs-cache:",
|
EVALS_RUNS_PREFIX: "kb-dashboard-evals-runs-cache:",
|
||||||
EVALS_RESULTS_PREFIX: "kb-dashboard-evals-results-cache:",
|
EVALS_RESULTS_PREFIX: "kb-dashboard-evals-results-cache:",
|
||||||
MISSIONS_PREFIX: "kb-dashboard-missions-cache:",
|
MISSIONS_PREFIX: "kb-dashboard-missions-cache:",
|
||||||
MISSIONS_SELECTED_ID_PREFIX: "kb-dashboard-mission-selected-cache:",
|
|
||||||
MAILBOX_INBOX_PREFIX: "kb-dashboard-mailbox-inbox-cache:",
|
MAILBOX_INBOX_PREFIX: "kb-dashboard-mailbox-inbox-cache:",
|
||||||
MAILBOX_OUTBOX_PREFIX: "kb-dashboard-mailbox-outbox-cache:",
|
MAILBOX_OUTBOX_PREFIX: "kb-dashboard-mailbox-outbox-cache:",
|
||||||
MAILBOX_UNREAD_COUNT_PREFIX: "kb-dashboard-mailbox-unread-cache:",
|
MAILBOX_UNREAD_COUNT_PREFIX: "kb-dashboard-mailbox-unread-cache:",
|
||||||
|
|||||||
Reference in New Issue
Block a user