Pular para conteúdo

UI Contract (page_contract)

Namespace: local_middag\framework\shared\ui. Extension proxy: local_middag\base\ui\page_contract.


Overview

page_contract is the universal server-driven UI protocol for MIDDAG pages (ADR-807). Every page rendered via Inertia — whether generated by page_builder or assembled manually — produces a single page_contract envelope. The frontend receives this JSON and resolves shells, layouts and blocks through registries. The backend describes intent, data and allowed actions. The frontend decides materialization.

The contract is @internal by default. Extensions consume it through the base\ui\ proxy layer.


JSON Schema

{
  "version": "1",
  "shell": "product",
  "page": {
    "key": "segments.index",
    "title": "Segments",
    "subtitle": "Audience segmentation engine",
    "breadcrumbs": [],
    "actions": []
  },
  "layout": {
    "template": "stack",
    "regions": {
      "header": [],
      "content": [],
      "footer": []
    }
  },
  "resources": {
    "auth": {},
    "capabilities": [],
    "featureFlags": {},
    "locale": "pt-BR"
  }
}

Top-level keys

Key Type Required Description
version string yes Contract version ("1")
shell string yes Macro frame of the experience
page object yes Page identity, breadcrumbs, actions
layout object yes Structural template + named regions
resources object no Shared transversal data (auth, locale)

Shells

The shell key resolves to an entry in shellRegistry. It defines the macro chrome around the page.

Shell Purpose Default
product Full MIDDAG experience — 3-level sidebar, full-width content yes
admin Moodle admin integration — contextual sidebar, standard content no
course Course/cohort experiences — no sidebar, standard content no
immersive Focused flows — no sidebar, full-width, minimal chrome no

Navigation for the product shell sidebar is transported as an Inertia shared prop, separate from the page contract. Extensions register navigation items via navigation_registry_interface during boot().


Layout Templates

The layout.template key resolves to an entry in layoutRegistry. Each template defines which region names are valid.

Active templates

Template Regions Use case
stack header, content, footer? Forms, linear pages
split header, main, aside Side-by-side inspection
dashboard header, content, aside? Home, status, overview
master-detail header, content, detail List + detail

Reserved templates (declared, not yet implemented)

Template Regions Future use
wizard steps, content, actions Multi-step forms, onboarding flows
canvas toolbar, canvas, inspector Visual builders (workflow, form builder)

Region names outside the template catalog are invalid. The backend describes region content; the frontend does not receive grid instructions beyond the template key.


Regions

Regions are named slots within a layout template. Each region holds an ordered array of block_descriptor objects.

Standard region names

Region Purpose
header Top strip — status, KPIs, page header
content Primary content area
footer Bottom strip (optional in stack)
aside Sidebar content (split, dashboard)
main Primary pane in split
detail Detail pane in master-detail
steps Step indicators (reserved: wizard)
actions Step actions (reserved: wizard)
toolbar Toolbar strip (reserved: canvas)
canvas Main canvas area (reserved: canvas)
inspector Property inspector (reserved: canvas)

Block Types

A block_descriptor is the composable unit inside a region. The type key resolves to a React component via blockRegistry.

block_descriptor shape

Field Type Required Description
type string yes Block type key in blockRegistry
key string yes Unique instance identifier
data object yes Typed payload (shape depends on type)
variant string no Visual/semantic variation
title string no Block heading
subtitle string no Short supporting text
actions array no Local actions (page_action[])
meta object no Non-visual metadata

Standard block catalog

Type Purpose Key data fields
dense_table Operational listings columns, rows, pagination, sort, filters, rowActions, bulkActions
form_panel Schema-driven forms action, method, schema, values, errors, meta
detail_panel Contextual read view sections
metric_card KPI summary value, label, delta?, icon?, href?
empty_state Empty state with CTA variant, description?, cta?, icon?
status_strip Status badges/strips items, tone?
activity_timeline Recent history groups, has_more?, load_more_href?
markdown_panel Short explanatory content content, max_height?

dense_table data shape

{
  "columns": [
    {"key": "name", "label": "Name", "sortable": true, "searchable": true},
    {"key": "status", "label": "Status", "variant": "badge"}
  ],
  "rows": [],
  "pagination": {"page": 1, "perPage": 25, "total": 0, "lastPage": 1},
  "sort": {"column": "created_at", "direction": "desc"},
  "filters": {"available": [], "applied": {}},
  "rowActions": [],
  "bulkActions": []
}

form_panel data shape

{
  "action": "/local/middag/index.php/segments",
  "method": "post",
  "schema": [],
  "values": {},
  "errors": {},
  "meta": {"multiStep": false, "cancelHref": "/local/middag/index.php/segments"}
}

The schema array follows the inertia_renderer serialization (ADR-805): nodes of {kind, component, props} for fields and {kind, id, label, children} for sections/groups.

Custom blocks (extensions)

Extensions register custom block types by implementing block_type_interface (PHP) and registering the corresponding React component in blockRegistry. Custom blocks are resolved identically to standard blocks.


Page Actions

page_action describes an actionable button at page level or block level.

Field Type Required Description
id string yes Stable action key
label string yes Visible label
intent string yes primary, secondary, danger, ghost
href string no Target URL or endpoint
method string no get, post, put, delete
icon string no Semantic icon identifier
requiresConfirmation bool no Requires explicit user confirmation
disabled bool no Visually unavailable

breadcrumb describes a single entry in the navigation trail.

Field Type Required Description
label string yes Display text
href string no Navigation target
external bool no Opens in new tab if true

Resources

page_resources carries minimal transversal data shared across the page. Heavy or page-specific data belongs in block_descriptor.data.

Field Type Description
auth object Current user, tenant, active context
capabilities list<string> Relevant capabilities for the shell
featureFlags map<string,bool> Rollout toggles
locale string Effective UI locale (default pt-BR)

PHP Classes

Class Role
shared\ui\page_contract Top-level envelope (readonly, JsonSerializable)
shared\ui\page_meta Page identity (key, title, breadcrumbs, actions)
shared\ui\layout_descriptor Template + regions map
shared\ui\block_descriptor Typed block within a region
shared\ui\page_action Action button descriptor
shared\ui\breadcrumb Navigation breadcrumb entry
shared\ui\page_resources Shared page resources
shared\ui\block Static factory for standard block types
shared\ui\page_builder Composable builder producing page_contract
base\ui\page_contract Public proxy for extensions

Restrictions

The contract does not transport:

  • React component names (use type keys resolved via registries)
  • className, style or arbitrary CSS
  • Nested component trees
  • Free-text visibility expressions
  • Inline JS handlers

Cross-references

  • ADR-807 — Frontend interface architecture and dynamic layout
  • ADR-805 — Form serialization and inertia_renderer
  • ADR-806 — Form DSL
  • ADR-803 — Rendering modes (Moodle pages, Inertia SPA, API JSON)