Frontend configuration
The frontend is the @orbyn-technologies/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:
npm install @orbyn-technologies/genie-engine-ui react react-domFont Awesome glyphs used by Genie navigation, views, actions and reports are bundled with the UI package. Hosts do not need a separate Font Awesome dependency or CDN stylesheet.
import { createGenieApp } from "@orbyn-technologies/genie-engine-ui";import "@orbyn-technologies/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.
Core config
Section titled “Core config”| Key | Purpose |
|---|---|
apiBase |
Base path where the Genie.Engine JSON API is mounted, e.g. /api/v1/genie. |
routing |
"browser" (History API paths like /object/Orders?Status=Active) or "hash". Browser routing requires the host to serve index.html for unknown paths (SPA fallback) — UseGenieSpa provides that out of the box. 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. |
pdfWorkerSrc |
URL of the pdf.js worker used by the document viewer when rendering a PDF. Omit to use the worker bundled inside the package (correct for almost every host); set it to a self-served copy (e.g. "/pdf.worker.min.mjs") when a strict CSP forbids the bundled worker’s blob: URL. |
handlers |
Render-time extension points (see below). |
auth |
Customizes the auth screens’ brand panel (see below). |
assistant |
Content overrides for the AI Assistant panel — { title, subtitle, welcome, suggestions, placeholder }. welcome/suggestions fall back to the server’s Genie:Assistant:WelcomeMessage/WelcomeChips (GET api/assistant/config), then defaults — with no built-in chips. See Assistant configuration. |
preloader |
Replaces the built-in loading splash (see below). |
Modules
Section titled “Modules”modules toggles which feature modules are compiled in (unused ones tree-shake out). Defaults:
auth, navbar, tables, forms, wizard, reports, 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, reports: true, // /reports/{slug} pages — see /reports/overview/ 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: report pages, the AI assistant, the global header search, and the notification bell under Notifications.
Navbar source
Section titled “Navbar source”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 /navbarcreateGenieApp({ 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" } });Auth brand panel
Section titled “Auth brand panel”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.
Loading splash (preloader)
Section titled “Loading splash (preloader)”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.});Request de-duplication
Section titled “Request de-duplication”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
GETplus 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: trueto log each request (with a+Nmsdelta, a caller-stack hint, and a⇊dedupmarker for coalesced calls) while diagnosing — leave it off in production.
Render extension handlers
Section titled “Render extension handlers”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" }, // Rich cell/value renderers, keyed by a column/field's Render="key" (see below). renderers: { code: ({ value }) => <span className="genie-badge genie-font-mono">{value}</span>, }, },});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.
Rich cell renderers
Section titled “Rich cell renderers”A grid column (or a view field) can render its value as rich content instead of plain text via
the Render="key" attribute. The engine ships four built-ins that need no registration:
badge— the value as one coloured chiptags— a pipe/comma-separated value as a row of chipsbullets— a pipe/comma-separated value as a bulleted listprogress— a numeric0–100value as a progress bar with a percentage label
Colour comes from the column’s StyleClasses / StyleClassesExpression (the gx-* palette), e.g.
Render="badge" with StyleClassesExpression="If(OnHand <= ReorderLevel, 'gx-danger', 'gx-success')".
For anything else, register a renderer under handlers.renderers; the Render key resolves to your
renderer first (a host entry wins over a built-in of the same name), then the built-in set, then
falls back to plain text if unknown. Each renderer receives
{ value, classes, row?, column?, field? } and returns a React node:
renderers: { // value is passed as a text CHILD → React escapes it. Never return an HTML string or build // markup from value; there is no dangerouslySetInnerHTML anywhere in the renderer path. code: ({ value }) => <span className="genie-badge genie-font-mono">{value}</span>, rating: ({ value }) => <span aria-label={`${value} stars`}>{"★".repeat(Number(value) || 0)}</span>,},