fix(renderer): simplify sidebar and reset page errors
This commit is contained in:
1 parent
eabd304a45
commit
6ce7e63c71
6 files changed
+79
-50
No files matched your search
@@ -66,11 +66,33 @@ export function useReducedMotion() {
|
||||
/* ErrorBoundary */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export class ErrorBoundary extends Component<{ children: ReactNode; fallback?: ReactNode }, { hasError: boolean; error: Error | null }> {
|
||||
constructor(props: { children: ReactNode; fallback?: ReactNode }) {
|
||||
type ErrorBoundaryResetKey = string | number | null | undefined;
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
resetKey?: ErrorBoundaryResetKey;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
resetKey: ErrorBoundaryResetKey;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
this.state = { hasError: false, error: null, resetKey: props.resetKey };
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: ErrorBoundaryProps, state: ErrorBoundaryState) {
|
||||
if (props.resetKey !== state.resetKey) {
|
||||
return { hasError: false, error: null, resetKey: props.resetKey };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useAppStore } from "../state/store";
|
||||
import { useFocusTrap, useReducedMotion } from "../components/ui";
|
||||
import { SquaresFour } from "../components/ui/icons";
|
||||
import {
|
||||
SETTINGS_NAV_ITEM_DEF,
|
||||
COMMAND_PALETTE_NAV_ITEM_DEFS,
|
||||
SIDEBAR_NAV_GROUP_DEFS,
|
||||
type NavItemDef,
|
||||
} from "../navigation";
|
||||
@@ -44,8 +44,8 @@ export function useUIController() {
|
||||
[sidebarGroups],
|
||||
);
|
||||
const navItems: NavItem[] = useMemo(
|
||||
() => sidebarNav.concat(toNavItem(SETTINGS_NAV_ITEM_DEF, t)),
|
||||
[sidebarNav, t],
|
||||
() => COMMAND_PALETTE_NAV_ITEM_DEFS.map((item) => toNavItem(item, t)),
|
||||
[t],
|
||||
);
|
||||
|
||||
const {
|
||||
|
||||
@@ -60,55 +60,26 @@ export const SECTION_NAV_IDS = Object.keys(SECTION_NAV_ITEM_DEFS) as SectionId[]
|
||||
|
||||
export const SIDEBAR_NAV_GROUP_DEFS = [
|
||||
{
|
||||
id: "company",
|
||||
id: "primary",
|
||||
items: [
|
||||
SECTION_NAV_ITEM_DEFS.overview,
|
||||
SECTION_NAV_ITEM_DEFS.inbox,
|
||||
SECTION_NAV_ITEM_DEFS.standup,
|
||||
SECTION_NAV_ITEM_DEFS.performance,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "organization",
|
||||
items: [
|
||||
SECTION_NAV_ITEM_DEFS.agents,
|
||||
SECTION_NAV_ITEM_DEFS.hiring,
|
||||
SECTION_NAV_ITEM_DEFS.goals,
|
||||
SECTION_NAV_ITEM_DEFS.projects,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "execution",
|
||||
items: [
|
||||
SECTION_NAV_ITEM_DEFS.tasks,
|
||||
SECTION_NAV_ITEM_DEFS.approvals,
|
||||
SECTION_NAV_ITEM_DEFS.runs,
|
||||
SECTION_NAV_ITEM_DEFS.costs,
|
||||
SECTION_NAV_ITEM_DEFS.activity,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "operations",
|
||||
items: [
|
||||
SECTION_NAV_ITEM_DEFS.workspaces,
|
||||
SECTION_NAV_ITEM_DEFS.documents,
|
||||
SECTION_NAV_ITEM_DEFS.knowledge,
|
||||
SECTION_NAV_ITEM_DEFS.automation,
|
||||
SECTION_NAV_ITEM_DEFS.connectors,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "collaboration",
|
||||
id: "company",
|
||||
items: [
|
||||
SECTION_NAV_ITEM_DEFS.agents,
|
||||
SECTION_NAV_ITEM_DEFS.communication,
|
||||
SECTION_NAV_ITEM_DEFS.meetings,
|
||||
SECTION_NAV_ITEM_DEFS.sprints,
|
||||
SECTION_NAV_ITEM_DEFS.social,
|
||||
SECTION_NAV_ITEM_DEFS.skills,
|
||||
SECTION_NAV_ITEM_DEFS.documents,
|
||||
],
|
||||
},
|
||||
] as const satisfies readonly NavGroupDef[];
|
||||
|
||||
export const COMMAND_PALETTE_NAV_ITEM_DEFS = SECTION_NAV_IDS.map((id) => SECTION_NAV_ITEM_DEFS[id]);
|
||||
|
||||
export const SETTINGS_NAV_ITEM_DEF = SECTION_NAV_ITEM_DEFS.settings;
|
||||
|
||||
export const DASHBOARD_NAV_TARGETS = [
|
||||
|
||||
@@ -38,7 +38,7 @@ export function SectionContentView({ section, error, pageTransition }: SectionCo
|
||||
) : null}
|
||||
|
||||
<div className={isCommunication ? "flex min-h-0 flex-1 flex-col" : "min-h-0 flex-1"}>
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary resetKey={section}>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={section}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ErrorBoundary } from "../../src/renderer/components/ui";
|
||||
|
||||
describe("ErrorBoundary", () => {
|
||||
it("resets a captured page error when the section changes", () => {
|
||||
const error = new Error("boom");
|
||||
const failedState = {
|
||||
hasError: true,
|
||||
error,
|
||||
resetKey: "runs",
|
||||
};
|
||||
|
||||
expect(ErrorBoundary.getDerivedStateFromProps({ children: null, resetKey: "tasks" }, failedState)).toEqual({
|
||||
hasError: false,
|
||||
error: null,
|
||||
resetKey: "tasks",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SectionId } from "@shared/types";
|
||||
import {
|
||||
COMMAND_PALETTE_NAV_ITEM_DEFS,
|
||||
DASHBOARD_NAV_TARGETS,
|
||||
SECTION_NAV_IDS,
|
||||
SETTINGS_NAV_ITEM_DEF,
|
||||
@@ -12,18 +13,34 @@ function sidebarSectionIds(): SectionId[] {
|
||||
}
|
||||
|
||||
describe("renderer navigation model", () => {
|
||||
it("gives every section a stable visible entry", () => {
|
||||
const visibleIds = [...sidebarSectionIds(), SETTINGS_NAV_ITEM_DEF.id];
|
||||
|
||||
expect(new Set(visibleIds).size).toBe(SECTION_NAV_IDS.length);
|
||||
expect(visibleIds).toEqual(expect.arrayContaining(SECTION_NAV_IDS));
|
||||
it("keeps the sidebar focused on the primary user journey", () => {
|
||||
expect(sidebarSectionIds()).toEqual([
|
||||
"overview",
|
||||
"inbox",
|
||||
"tasks",
|
||||
"approvals",
|
||||
"agents",
|
||||
"communication",
|
||||
"documents",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps Dashboard targets reachable from the stable navigation model", () => {
|
||||
const visibleIds = new Set([...sidebarSectionIds(), SETTINGS_NAV_ITEM_DEF.id]);
|
||||
it("keeps every section reachable from command navigation", () => {
|
||||
const commandIds = COMMAND_PALETTE_NAV_ITEM_DEFS.map((item) => item.id);
|
||||
|
||||
expect(new Set(commandIds).size).toBe(SECTION_NAV_IDS.length);
|
||||
expect(commandIds).toEqual(expect.arrayContaining(SECTION_NAV_IDS));
|
||||
});
|
||||
|
||||
it("keeps Dashboard targets reachable from command navigation", () => {
|
||||
const commandIds = new Set(COMMAND_PALETTE_NAV_ITEM_DEFS.map((item) => item.id));
|
||||
|
||||
for (const sectionId of DASHBOARD_NAV_TARGETS) {
|
||||
expect(visibleIds.has(sectionId)).toBe(true);
|
||||
expect(commandIds.has(sectionId)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps settings outside the scrollable sidebar groups", () => {
|
||||
expect(sidebarSectionIds()).not.toContain(SETTINGS_NAV_ITEM_DEF.id);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user