Skip to content

Frontend configuration

The frontend is the @mr-aybee/genie-engine-ui npm package. Install it (plus the React peer deps) and configure the whole app in code — there are no XML/config files on the client:

Terminal window
npm install @mr-aybee/genie-engine-ui react react-dom
import { createGenieApp } from "@mr-aybee/genie-engine-ui";
import "@mr-aybee/genie-engine-ui/styles.css";
const GenieApp = createGenieApp({
apiBase: "/api/v1/genie", // where the Genie.Engine API is mounted
routing: "browser",
navbar: { source: "endpoint" },
modules: { auth: true, notifications: true },
theme: { mode: "system" },
});

createGenieApp mounts GenieRouter, which maps /table|form|view/{name} to useGenieTable / useGenieForm and the matching component. The UI fetches structure (SQL-free schema) once and values per record, then merges them client-side — so a view defined in *.view.xml renders with no frontend code. Auth affordances adapt automatically to the server’s enabled features (read from GET /api/v1/auth/config). See Object endpoints for the exact contract, and Components for the rendered surface.

Key Purpose
apiBase Base path where the Genie.Engine JSON API is mounted, e.g. /api/v1/genie.
routing "browser" (History API paths like /table/Orders?Status=Active) or "hash". Browser routing requires the host to serve index.html for unknown paths (SPA fallback). Auth/account pages always use the hash.
basePath Base path the app is mounted under (e.g. /genie). Defaults to /.
modules Which feature modules to include (see below). Omitted flags default per module.
navbar Where the sidebar navbar comes from (see below).
theme Theme overrides — see Theming.
schemaCacheTtlSeconds Lifetime of the in-memory schema (metadata) cache. null (default) holds it until reload; 0 disables it.
debugRequests Logs every API request to the console (delta, caller hint, ⇊dedup marker). Off by default.
handlers Render-time extension points (see below).
auth Customizes the auth screens’ brand panel (see below).
preloader Replaces the built-in loading splash (see below).

modules toggles which feature modules are compiled in (unused ones tree-shake out). Defaults: auth, navbar, tables, forms, wizard, notifications and multiTenant are on; assistant and headerSearch are off.

createGenieApp({
apiBase: "/api/v1/genie",
modules: {
auth: true,
navbar: true,
tables: true,
forms: true,
wizard: true,
notifications: true, // header bell — see /platform/notifications/
assistant: true, // AI assistant panel — see /assistant/overview/
headerSearch: true, // global search — see /platform/search/
multiTenant: false,
},
});

Some modules are documented in their own sections: the AI assistant, the global header search, and the notification bell under Notifications.

navbar selects where the sidebar tree comes from. There is no auto-fallback between modes — an endpoint error stays an error, and static always uses items. Omit the whole block to use the package-provided default nav items (the built-in System module).

// (a) Server-driven: fetch the permission-filtered tree from GET /navbar
createGenieApp({ apiBase: "/api/v1/genie", navbar: { source: "endpoint" } });
// (b) Host-supplied static tree (no API call)
createGenieApp({ apiBase: "/api/v1/genie", navbar: { source: "static", items: myNavItems } });

modules.navbar toggles whether the rail/sidebar renders at all; navbar.source selects where its contents come from — they’re independent.

All theming — mode, accent/accents, logo/monogram, custom colours — flows through the theme block on createGenieApp. The palette and token detail live in Theming.

createGenieApp({ apiBase: "/api/v1/genie", theme: { mode: "system", accent: "orbyn" } });

The login / forgot / reset screens render in a split layout: a gradient brand panel beside the form. Customize it via the auth block — no fork needed (brandPanel wins over brandContent when both are set):

createGenieApp({
apiBase: "/api/v1/genie",
auth: {
// (a) Replace the whole panel with your own markup. The host owns everything in the
// `.auth-brand` slot; style it with host CSS.
brandPanel: <MyBrandPanel />,
// (b) …or keep the default panel chrome and only swap its copy:
brandContent: {
heading: "Inventory operations, unified.",
tagline: "Stock, suppliers, and purchase orders — in one workspace.",
features: ["Real-time stock levels", "Supplier management", "Purchase-order delivery"],
},
},
});

The panel’s logo/wordmark follows theme.logo and its colours follow the theme tokens. For a fully-custom panel see the Inventory sample’s InventoryBrandPanel.tsx.

While the session resolves and the authenticated app-shell chunk downloads, Genie shows a built-in loading splash (branded via theme.logo). Replace it wholesale with the top-level preloader slot — the same visual then covers the session-resolve, the lazy-shell Suspense fallback, and the post-login hand-off:

createGenieApp({
apiBase: "/api/v1/genie",
preloader: <MyPreloader />, // any ReactNode; own markup + CSS. Omit to keep the built-in splash.
});

GenieApiClient collapses redundant calls so a single view/form load makes one network round-trip per logical request — important under React StrictMode (which double-invokes effects in dev) and at production scale:

  • Metadata is cached in-memory (schemaCacheTtlSeconds; held until reload by default).
  • In-flight coalescing dedupes concurrent identical requests: every GET plus the read-style POSTs (/table, /form, /view, /field-dataset, /sequence-number) share one fetch while it is pending. There is no caching beyond the in-flight window — once a request settles, a later navigation re-fetches fresh data. Mutations (submit / delete-row / upload) are never coalesced.
  • Set debugRequests: true to log each request (with a +Nms delta, a caller-stack hint, and a ⇊dedup marker for coalesced calls) while diagnosing — leave it off in production.

Pass a handlers block to customize table/form rendering without forking the components. Every table/form render invokes these (all fields optional):

createGenieApp({
apiBase: "/api/v1/genie",
handlers: {
// Extra functions available to EVERY client-side expression — table `StyleClassesExpression`
// and form field Required/Disabled/Hidden/Value rules. Merged over the built-ins (If/IIF,
// sumLines, …), so a host entry can override a built-in of the same name.
expressionFunctions: {
Money: (n) => new Intl.NumberFormat(undefined, { style: "currency", currency: "USD" }).format(Number(n) || 0),
},
// Per-cell CSS class resolver, evaluated per row. Appended after the column's StyleClasses and
// its evaluated StyleClassesExpression.
cellClass: (column, row) => (column.Name === "Status" && row.Cells.Status === "Overdue" ? "gx-danger" : null),
// Override class names for known render slots whose styling varies by project.
classNames: { attachmentCell: "my-attachment-row" },
},
});

The built-in If(cond, a, b) (alias IIF) mirrors the DataColumn IIF syntax authors already know and uses = for comparison — e.g. StyleClassesExpression="If(Status = 'Overdue', 'gx-danger', 'gx-success')". See the <Column> attributes in Model Authoring and the field rules in Components.