Skip to main content

Shell Context & State Management API

This reference documents the React hooks and context injected by the Framework M Desk shell. Use these hooks when building Micro-Frontends (MFEs), plugin components, or custom page overrides so that generated code matches the actual runtime contract.


1. useShell()

Access the global shell context inside any component rendered below <ShellProvider>.

import { useShell } from "@framework-m/desk";

function MyWidget() {
const shell = useShell();
// shell.isMobile, shell.registerSection, ...
}

ShellContextValue

PropertyTypeDescription
isMobilebooleanWhether the shell is rendering in mobile viewport mode.
isGuestbooleanWhether the current session is an anonymous guest session.
rightSidebarOpenbooleanCurrent open/closed state of the right sidebar.
setRightSidebarOpen(value: boolean) => voidSetter for the right sidebar state.
focusModebooleanWhether focus mode (distraction-free UI) is active.
setFocusMode(value: boolean) => voidExplicitly enable/disable focus mode.
toggleFocusMode() => voidToggle focus mode.
registerSection(section: ShellSection) => () => voidRegister a navigation section. Returns an unregister function.
getSections(slot: ShellSection["slot"]) => ShellSection[]Retrieve sections for a given navbar/sidebar slot.
registerOmniSearchProvider(provider: OmniSearchProvider) => () => voidRegister a global omni-search source.
searchOmni(query: string) => Promise<OmniSearchResult[]>Execute omni-search across all registered providers.
setRealtimeAdapter(adapter: RealtimeAdapter | null) => Promise<void>Swap the realtime/WebSocket adapter at runtime.
subscribeRealtime(room: string, listener: RealtimeSubscriber) => () => voidSubscribe to a realtime room. Returns an unsubscribe function.
publishRealtime(room: string, type: string, payload: Record<string, unknown>) => Promise<void>Publish a message to a realtime room.

ShellSection

interface ShellSection {
id: string;
slot: "navbar-left" | "navbar-right" | "sidebar";
order?: number;
label?: string;
icon?: string;
route?: string;
module?: string;
render?: () => ReactNode;
}

OmniSearchResult

interface OmniSearchResult {
id: string;
label: string;
route: string;
}

1.1 Auth & Login Guard: AuthenticatedLoginGuard

Use AuthenticatedLoginGuard in custom public or login routes to prevent already-authenticated users from seeing the login screen, while preserving target query parameters (?to= or ?redirect=).

import { AuthenticatedLoginGuard } from "@framework-m/desk";

// Custom public login route in plugin.config.ts or shell app
<Route
path="/portal/login"
element={
<AuthenticatedLoginGuard
loginPage={<CustomPortalLoginPage />}
defaultTarget="/portal/dashboard"
/>
}
/>

AuthenticatedLoginGuardProps

PropertyTypeDefaultDescription
loginPageReactNode<LoginPage />Login page component rendered when the user is NOT authenticated.
defaultTargetstring"/app/dashboard"Fallback route when no ?to= or ?redirect= query param is present.

2. Form State: useFormController

Standard hook for reading and mutating a DocType record in a form view.

import { useFormController } from "@framework-m/desk";

function TaskEditor({ docname }: { docname: string }) {
const { frm, schema, metaLoading, metaError } = useFormController({
doctype: "Task",
id: docname,
});

if (metaLoading) return <div>Loading...</div>;
if (metaError) return <div>Error: {metaError.message}</div>;

return (
<>
<input
value={(frm.doc.title as string) ?? ""}
onChange={(e) => frm.setDoc({ title: e.target.value })}
/>
<button onClick={() => frm.save()} disabled={frm.isSaving}>
Save
</button>
</>
);
}

UseFormControllerProps

PropertyTypeDescription
doctypestringDocType to load. Defaults to the doctype route param.
idstringRecord identifier. "me" resolves to the current user. Defaults to the id route param.

Frm object returned in { frm }

PropertyTypeDescription
docRecord<string, unknown>Current document values.
setDoc(update: Partial<T> | ((prev: T) => T)) => voidPatch document state.
save(options?, overrideValues?) => Promise<void>Persist the document.
cancel() => voidCancel editing and navigate back to list.
isEditingbooleanWhether an existing record is being edited.
isSavingbooleanWhether a save mutation is in flight.
isLockedbooleanWhether the document is submitted/cancelled (docstatus 1 or 2).
errorsstring[]Top-level error messages from the last failed save.
noticesstring[]Informational notices.
validationErrorValidationError | nullStructured validation errors from the backend.
workflowunknownWorkflow state if applicable.
refetch() => Promise<void>Refetch the current record.

3. DocType Metadata: useDocTypeMeta

Fetch schema, layout, permissions, and workflow metadata for a DocType.

import { useDocTypeMeta } from "@framework-m/desk";

function TaskMeta() {
const { schema, layout, permissions, isLoading, error } = useDocTypeMeta("Task");
// ...
}

UseDocTypeMetaResult

PropertyTypeDescription
schemaDocTypeSchema | undefinedJSON Schema for the DocType.
layoutLayoutConfig | undefinedLayout configuration for form rendering.
permissionsDocTypePermissions | undefinedCurrent user's permissions.
workflowWorkflowConfig | undefinedWorkflow transitions and states.
metaDocTypeMeta | undefinedFull raw metadata object.
isLoadingbooleanLoading state.
errorError | nullError if fetch failed.
refetch() => voidTrigger a metadata refetch.

4. RPC Calls: useCall

General-purpose hook for calling RPC endpoints or arbitrary API paths.

import { useCall } from "@framework-m/desk";

function ApproveButton({ docnames }: { docnames: string[] }) {
const { execute, isLoading, data, error } = useCall<{ message: string }>();

const approve = async () => {
const result = await execute({
doctype: "Task",
method: "bulk_approve",
payload: { docnames },
});
if (result) {
// success
}
};

return <button onClick={approve} disabled={isLoading}>Approve</button>;
}

UseCallResult<T>

PropertyTypeDescription
execute(args: CallArgs) => Promise<T | null>Execute the request.
isLoadingbooleanRequest in-flight flag.
dataT | nullLast successful response data.
errorstring | nullLast error message.

CallArgs

Either an RPC-style call:

interface RPCCallArgs {
doctype: string;
method: string;
payload?: unknown;
httpMethod?: string;
}

Or a path-based call:

interface PathCallArgs {
path: string;
method?: string;
payload?: unknown;
}

5. View Customization: Simple (Low-Code) vs Expert Modes

DESIGN PARADIGM

Framework M supports a 2-tier architecture: Simple / Low-Code Mode for 80% standard CRUD screens, and Expert Mode for 20% custom enterprise React screens. Top breadcrumbs and shell layout features are maintained at the shell level for both modes.


Tier 1: Simple / Low-Code Mode (Fast Prototyping)

For 80% of standard DocTypes (ItemGroup, TaxRate, Company, LocalUser), zero React code is required. Views render automatically from DocType metadata schemas.

When a low-code developer needs to tweak a specific view, they declare a component override in plugin.config.ts while using Frappe-style frm controller helpers (frm.setDocValue(), frm.save(), frm.doc):

// plugin.config.ts (Low-Code View Override)
const plugin: FrameworkMPlugin = {
name: "wms",
version: "0.1.0",
doctypes: [
{
doctype: "StockEntry",
components: {
FormView: WmsStockEntryFormView, // Custom low-code form view override
},
},
],
};

// Form scripting with Frappe-style `frm` helper:
export function WmsStockEntryFormView({ doctype, id }: { doctype: string; id?: string }) {
const { frm, schema } = useFormController({ doctype, id });

const handleQuickSubmit = () => {
frm.setDocValue("docstatus", 1);
frm.save();
};

return (
</div>
);
}

Auto-Discovery Rules (Directory-based vs plugin.config.ts)

To avoid developer confusion, choose one of the following registration patterns:

  1. Automatic Directory Discovery (src/overrides/[DocType]/): @framework-m/vite-plugin automatically detects override components placed in standard directory locations—no manual registration in plugin.config.ts required:

    • src/overrides/StockEntry/FormView.tsx (Form view override)
    • src/overrides/StockEntry/KanbanView.tsx (Kanban view override)
    • src/overrides/Core/LoginPage.tsx (Global login page override)
  2. Explicit Declaration (plugin.config.ts): Use doctypes[].components in plugin.config.ts when your component lives outside src/overrides/ (e.g. src/pages/WmsStockEntryFormView.tsx).

DISCOVERY CONVENTION

Do not double-register the same component in both src/overrides/[DocType]/ and plugin.config.ts. Choose either convention per plugin.


Tier 2: Expert Mode (React Routes & Atomic Primitives)

For 20% complex enterprise screens (e.g. SupplierProfilePage with maps, charts, or Tamagui stacks), senior developers declare standard React routes in plugin.config.ts and compose pages using atomic primitives (FormActions, FormFields, ListSearchInput, ListPagination, KanbanCardItem, TreeNodeItem):

// plugin.config.ts (Expert React Route Declaration)
const plugin: FrameworkMPlugin = {
name: "supplier_app",
version: "0.1.0",
routes: [
{
path: "/supplier_app/SupplierProfile/:id",
element: () => import("./pages/SupplierProfilePage"),
},
],
};

// pages/SupplierProfilePage.tsx (Expert Composition)
import { useFormController, FormActions, FormFields } from "@framework-m/desk";
import { YStack, XStack, Heading, Card } from "@framework-m/ui";

export function SupplierProfilePage() {
const controller = useFormController({ doctype: "SupplierProfile" });

return (
<YStack gap="$md">
<Card padding="$md">
<Heading size="$md">{controller.frm.doc?.supplier_name || "Supplier Profile"}</Heading>
<FormActions controller={controller} />
</Card>

<XStack gap="$lg">
<FormFields controller={controller} />
{/* Custom React / Map widget */}
<MyMapWidget location={controller.frm.doc?.location} />
</XStack>
</YStack>
);
}

6. List View Actions: useListActions

State helpers for list views (column preferences, view mode, row click navigation).

import { useListActions } from "@framework-m/desk";

function TaskList() {
const {
activeView,
setActiveView,
handleRowClick,
columnPreferences,
toggleColumnVisibility,
isMobile,
} = useListActions({
doctype: "Task",
canKanban: true,
canTree: false,
canGantt: false,
canCalendar: false,
});
// ...
}

UseListActionsProps

PropertyTypeDescription
doctypestringDocType being listed.
propViewListViewModeOptional controlled view mode.
canKanbanbooleanWhether kanban view is available.
canTreebooleanWhether tree view is available.
canGanttbooleanWhether gantt view is available.
canCalendarbooleanWhether calendar view is available.
resolveDetailRoute(doctype, id, row) => string | undefinedOptional custom detail route resolver.

7. Routing & Navigation

The shell does not expose imperative navigation helpers on ShellContextValue. Use React Router directly:

import { useNavigate, Link } from "react-router";

function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
const navigate = useNavigate();
return <button onClick={() => navigate(to)}>{children}</button>;
}

Use the exported <Breadcrumb /> component from @framework-m/desk to render breadcrumbs driven by @refinedev/core's useBreadcrumb hook.


8. Feedback Components

For notifications, toasts, and confirmations, use the feedback primitives exported by @framework-m/desk:

  • AlertBanner — inline status/error/success banner.
  • ConfirmDialog — modal confirmation dialog.
  • SubmittedBanner — banner shown after successful submission.

There is no useToast() hook in @framework-m/desk.