Skip to content

Page Contracts

A PageContract is a JSON object that fully describes a page. The backend builds it; the frontend renders it.

Contract Anatomy

Every contract follows the same hierarchical structure:

PageContract
 +-- version: "1"
 +-- shell: "product" | "admin"
 +-- page
 |    +-- key, title, breadcrumbs, actions, filters
 +-- layout
      +-- template: "stack" | "split" | "dashboard" | ...
      +-- regions
           +-- content: [ BlockDescriptor, ... ]
           +-- sidebar: [ BlockDescriptor, ... ]
           +-- header:  [ BlockDescriptor, ... ]

BlockDescriptor
 +-- type: "dense_table" | "form_panel" | ...
 +-- key:  unique string
 +-- data: { ... block-specific payload }

Immutable at render time

The contract is read-only once it reaches the frontend. All mutations happen on the backend, which sends a new contract via Inertia partial reload.

Shells

The shell wraps the entire page and provides chrome (navigation, header, footer):

ShellDescriptionChrome
productFull application shell with sidebar navigationSidebar + top header + breadcrumbs
adminSimplified shell for admin/settings pagesSimple header only
courseCourse-scoped shell (Moodle-specific)Course sidebar + header
immersiveDistraction-free shell for focused tasksMinimal header with close button

Layouts

The layout defines how regions are arranged inside the shell content area:

TemplateDescriptionRegions
stackSingle column, blocks stacked verticallycontent
splitTwo-column layout (main + sidebar)content, sidebar
dashboardGrid with header row and body gridheader, content
master-detailList on the left, detail panel on the rightmaster, detail
wizardMulti-step form with progress indicatorsteps (array of step regions)
canvasFull-bleed visual editor canvascontent

See Layout Templates for detailed region documentation.

Blocks Overview

Blocks are the building blocks of every page. Each block type has a specific data shape. See the Block Catalog for full details.

Block TypeCategoryDescription
dense_tableData DisplaySortable, filterable data table with row actions
detail_panelData DisplayKey-value detail view with sections
card_gridData DisplayGrid of cards with status, actions, and metadata
link_listData DisplaySimple list of labeled links with optional icons
status_stripData DisplayHorizontal strip of status/metric badges
activity_timelineData DisplayChronological list of events
form_panelFormsDynamic form with field groups and validation
form_builderFormsDrag-and-drop form field designer
sentence_builderFormsNatural language rule composer
condition_treeFormsNested AND/OR condition tree editor
empty_stateContentPlaceholder for empty or first-use states
markdown_panelContentRendered markdown content block
tabbed_panelContentTabbed container for nested blocks
action_gridContentGrid of action cards (quick actions / shortcuts)
metric_cardVisualizationSingle KPI with trend indicator
flow_editorVisualizationVisual workflow/node graph editor

Rendering Flow

The journey from backend to rendered page:

1. Backend (PHP)        Build PageContract array/object
                           |
2. Inertia              Serialize to JSON, send as page props
                           |
3. ContractPage         Receive contract prop
                           |
4. Shell Registry       Resolve shell component (product/admin)
                           |
5. Layout Registry      Resolve layout component (stack/split/...)
                           |
6. Block Registry       For each BlockDescriptor in each region,
                        resolve the block component by type
                           |
7. React Render         Shell > Layout > Blocks rendered as tree

Custom registrations

The registries are populated by registerDefaults(). You can also register custom shells, layouts, or blocks for extension-specific UIs.

TypeScript Types

ts
interface PageContract {
    version: '1';
    shell: 'product' | 'admin' | 'course' | 'immersive';
    page: PageMeta;
    layout: LayoutDescriptor;
    resources?: PageResources;
}

interface PageMeta {
    key: string;
    title: string;
    subtitle?: string;
    icon?: string;
    badge?: PageBadge;
    favoritable?: boolean;
    breadcrumbs?: Breadcrumb[];
    actions?: PageAction[];
    filterTabs?: PageFilterTab[];
    activeFilterTab?: string;
}

interface LayoutDescriptor {
    template: 'stack' | 'split' | 'dashboard' | 'master-detail' | 'wizard' | 'canvas';
    regions: Record<string, BlockDescriptor[]>;
    meta?: Record<string, unknown>;
}

interface BlockDescriptor<TData = Record<string, unknown>> {
    type: string;       // block type key (e.g. 'dense_table')
    key: string;        // unique key within the page
    data: TData;        // typed data payload
    variant?: string;
    title?: string;
    subtitle?: string;
    actions?: PageAction[];
    meta?: Record<string, unknown>;
}

See API Reference for complete type definitions.

Released under the proprietary license.