Skip to content

Shell pages and Shell components

Module Suite 4.1.0 (Polaris) lets you build a Smart View perspective from a Shell Page and the Shell Components it mounts. A Shell Page coexists with a Classic Smart Page. Classic pages keep their current behavior, and both kinds of content can sit in the same perspective, in different tiles. You can therefore adopt Shell Pages one perspective, or one tile, at a time.

The Smart Page editor can create a Classic page, a Shell Page, or a Shell Component.

Tip

For every field, default, composable, and event, see Shell Page reference.

How a Shell Page is assembled

A Shell Page is rendered once by the server as a static layout. After that first render, the Shell owns every change to the page markup. One Shell runs per page: it finds the regions, resolves the components the page may mount, and mounts one Shell Component in each region.

flowchart TD
    Perspective["Smart View perspective"] --> HostTile["Tile hosting the Shell Page"]
    HostTile --> ShellPage["Shell Page (static layout)"]
    ShellPage --> Controller["Controller"]
    Controller --> Definition["Page definition: regions and component registry"]
    Controller --> ControllerRegion["Controller region (when Region ID is set)"]
    ShellPage --> RegionWidgets["Region widgets in the layout"]
    Definition --> Shell["Shell (one per page)"]
    Shell --> ControllerRegion
    Shell --> RegionWidgets
    ControllerRegion --> CompA["Shell Component"]
    RegionWidgets --> CompB["Shell Component"]
    CompB --> HostWidget["Embedded Smart View widget"]
    Perspective --> OtherTile["Other tile with a Shell Page"]
    OtherTile --> ExtraRegion["Contributed region"]
    Shell --> ExtraRegion

The controller produces the page definition: the regions, their initial components, and the registry of components the page can mount. The Shell mounts components into the controller region, into Region widgets, and into regions that other tiles contribute. A Shell Component can in turn embed a Smart View widget.

Keep the layout static

A Shell Page must not perform action roundtrips that replace its body. Such a roundtrip would destroy the mounted components without unmounting them. Put Classic widgets that need roundtrips in their own tile.

What a Shell Component is

A Shell Component is the piece of interface a Shell Page mounts into a region. You develop, version, and publish each component on its own. A page is assembled from those components. Each mounted component runs as its own Vue application.

A Shell Component is stored as a Smart Page flagged as a Shell Component, in any folder. Each version of that node is one build of the component. Pages reference a component by its component key, a stable readable identifier such as customer.orders. Regions, region policies, and region change requests address components by key. Only the page registry maps each key to the component's node id (or, for a quick test, to an explicit bundle URL).

Components coordinate through shared page state, events, and requests to change a region.

The Component AI Builder generates a Shell Component. You review the component key, the input properties, the capabilities, and the required permissions, then preview the component and deploy it. Generation is the start of that review.

The same Design Profiles are available to the Component AI Builder and to the AI Form Builder.

Building a component with the Component AI Builder

The AI may suggest the input properties, capabilities, and permissions. You decide the final values. What you authorize is stored with the component in its component.json. The server copies it into the registry entry of every page that mounts the component, and the Shell enforces it at run time. On Content Server, a page controller that sets these values on its own registry entry overrides them.

sequenceDiagram
    participant Dev as Developer
    participant Builder as Component AI Builder
    participant Host as Content Server or MSR
    participant Page as Shell Page
    Dev->>Builder: Describe the component
    Builder->>Builder: Generate source, strings, and suggested metadata
    Builder-->>Dev: Component for review
    Dev->>Builder: Review key, properties, capabilities, permissions
    Dev->>Builder: Preview
    Builder->>Builder: Compile and validate
    Builder-->>Dev: Errors block, warnings are listed
    Dev->>Builder: Deploy
    Builder->>Host: Store a new component version
    Page->>Host: Resolve the component by key

Generation produces a draft. Review, preview, and a successful compile come before deploy, and deploy stores a new version that pages then resolve by key.

Input properties

Input properties are the per-instance input of a component. Whoever mounts the component supplies them: the initial assignment of a region, or another component that requests a region change. A component reads its input properties and never changes them. When a region change keeps the same component key, the Shell updates the properties in place without mounting the component again, so watch them instead of reading them only when the component mounts.

Each property in the input schema can declare:

Field Purpose
name Property name
type Value type
required Whether the mounter must supply it
default Value used when the mounter supplies none
description What the property means
example Sample value
enumOptions Allowed values

The Shell validates the properties as passed against the schema, before any default is applied, and rejects a mount or change whose properties do not match. The component runtime then applies the defaults, so a required property must be supplied even when it declares a default. The component reads the properties with useInputProps().

Tip

Do not pass page-wide values through input properties. Use shared page state for values that several components read.

Capabilities and permissions

Capabilities are the Shell APIs a component may use, for example requesting region changes, calling REST, navigating, registering dirty state, publishing and subscribing to events, and embedding Smart View widgets (hostWidget). The Shell exposes only the capabilities you authorized. An API you did not grant is replaced by a stub that throws when the component calls it.

Permissions are checked at two levels:

  • Registry. The page registry is resolved on the server for the current user. A component the user cannot access on its node is left out of the registry.
  • Required permissions. These are application-level strings, not Content Server permission bits. They gate whether the component may be mounted: the user context of the page must hold all of them.

Warning

Required permissions shape the interface only. The backend remains the authority for real authorization, so check access again on the server for every write.

How components communicate

Components never call each other directly. They use three Shell-mediated channels:

Channel Use it for Notes
Shared page state (useSharedState) Values that outlive one component, such as the focused entity or the active view Keys and their initial values are declared by the page. A declared schema is advisory and not enforced. Reading and writing require the store capability. Some keys are read-only. The state survives the unmount and remount of any one component.
Events (useEvents) Fire-and-forget notifications Topics are strings. Prefixes shell:, region:, component:, and permissions: are reserved for the runtime.
Region change requests (useRegion) Asking the Shell to mount another component in a region The Shell decides. Policy, permissions, and the dirty check apply.

The Shell event bus is separate from the Classic ampagenotify channel. Events under an ampagenotify: key are forwarded to and from that channel, which is how a Shell Component talks to Classic tiles. There is no other mapping between the two buses.

Tip

If two components need each other's internals, move the shared value into shared page state or an event instead.

Regions and layout

A Shell Page mounts one component at a time in each region. The controller draws one styled region when its Region ID is set. That region can carry initial properties and a policy. A page that needs only that region uses the controller alone.

Layout replaces Row and Column. Further regions are Region widgets in the layout. The controller script receives the page definition when you inject it. Saving the page leaves the controller script as it is.

The published controller contains its region and the registry of components the page can mount.

The controller is the authority for regions. The page view may host none, some, or all of the regions the controller defines, because the Shell also finds regions outside the view.

Border, padding, margin, width, and height style the region host. Font and color style the host and reach the component by inheritance. The component itself mounts in a shadow root on the host, so page styles do not restyle it and its styles do not leak onto the page.

When two region hosts on the page carry the same region id, the engine ignores the duplicate host, so do not give a Region widget the id the controller already draws. An entry in the regions script that repeats the controller Region ID replaces the controller entry completely, including its initial properties and policy.

A page that is already published keeps its saved HTML until the next editor save. On that save, a Row or Column that the layout no longer contains becomes an HTML comment. A region placed beside that widget remains on the page.

Region policy and region changes

A component asks for a region change. The Shell decides whether to perform it. A region policy constrains which changes are allowed:

  • an allow list that names, by component key, what the region accepts
  • whether a change requested from the region itself is allowed (allowSelfReplacement). The initial mount counts as such a change, so allowSelfReplacement: false keeps a region with an initial component empty.
  • which regions may request a change (allowedSourceRegions)

Before a change, the Shell also checks permissions, validates the input properties, applies loop protection, and runs the dirty check. The initial mount of each region goes through the same policy and permission checks, without the dirty check. A region change request is awaited and returns its result.

A common pattern is a list that replaces itself with a form. The list calls requestSelfChange with a returnComponentKey. When the user completes the form's submit, or discards the form, the region returns to the list. Saving a partial form and other named form actions do not return. The region policy must allow the self-change: not mutable: false and not allowSelfReplacement: false.

Region composition lives in memory. It is not written to the URL. After a reload or a browser back, each region returns to its initial component. Navigation to a node is handed to Smart View.

Dirty state

A component registers unsaved changes with useDirtyState, passing its own region id (regionId from useShellContext()). Without it the registration is not linked to the region, and the region is not protected. An AI View mounted in a region registers its dirty state automatically.

The Shell applies the dirty check before it replaces the component in a region with a different component, or clears the region. An update that keeps the same component key is never checked. When the user leaves the page, the browser leave-page check gives best-effort protection. Navigation to another Smart View perspective is not guarded beyond that.

Regions in other tiles

Other tiles in the same perspective can contribute regions when they render Shell Pages. The Shell finds regions that are already rendered when it starts and watches for regions that arrive later. When a region disappears, its component is unmounted. Components mount once the user context is ready.

The page in the tile that hosts the Shell owns page-level policy and the component registry. A contributed region carries its own policy and initial assignment. A second hosting tile on the same page does not start a second Shell.

A Classic tile cannot be a region. It can exchange notifications with Shell Components through the ampagenotify bridge.

Isolation and failures

The Shell keeps these failures local:

  • Each component runs as its own Vue application.
  • A contributed region whose id collides with an existing region is rejected with a logged error. The page keeps running.
  • A call to an API the component was not granted throws in that component.
  • A perspective with contributed regions but no tile hosting the Shell mounts nothing.

Smart View widgets

A Shell Component can embed these Smart View widgets:

  • Nodes table — shows the nodes of a container. Omit the container to use the perspective node. Drag and drop is on unless you turn it off. A selection reports each node’s id, name, and type. A failure reports a message.
  • Document overview — shows the summary of one node. The node id is required. A failure reports a message.
  • Metadata — shows the properties and categories of one node, including editing. The node id is required. A failure reports a message.
  • Related workspaces — shows the related workspaces of a business workspace. The workspace is taken from the perspective unless you pass workspaceNodeId.
  • Workspace — shows a business workspace, given by its node id or by its business object. viewMode selects folder browse or full page. A failure reports a message.
  • Workspace header — shows the header of one business workspace. The workspace node id is required. A failure reports a message.

Each widget is a named Vue component in your component source:

Widget Component Properties Events
Nodes table CsuiNodeTable containerId (optional), enableDragAndDrop (default on) select, error
Document overview CsuiDocumentOverview nodeId (required) error
Metadata CsuiMetadata nodeId (required) error
Related workspaces CsuiRelatedWorkspaces title, workspaceTypeId, relationType, workspaceNodeId (all optional; workspaceNodeId is required off a business workspace perspective) error
Workspace CsuiWorkspace workspaceNodeId, or busObjectType, busObjectId, extSystemId; viewMode (folderBrowse or fullPage) error
Workspace header CsuiWorkspaceHeader workspaceNodeId (required) error

Event payloads are plain JSON. select carries the selected nodes, and error carries a message:

{ "nodes": [{ "id": 2000, "name": "Orders", "type": 0 }] }
{ "message": "..." }

These are Vue events only. To tell other components about a selection, publish an event from your component.

Embedding needs the hostWidget capability. The widget uses the live perspective, so Smart View default actions apply. A click in a nodes table can navigate Smart View away from the Shell Page. Do not assume your component still exists after such a click. The widgets emit no open event.

The widget markup sits outside the component shadow root, projected into the region host, so Smart View styles apply to it. The widget fills the slot it is given.

Changing a property of an embedded widget mounts that widget again. Events from the replaced mount are dropped. Because a remount fetches the data again, keep the properties you change few. The guest is removed when the component is removed or when its mount point is replaced.

Related workspaces takes its workspace from the business workspace perspective the Shell Page sits on. On any other perspective, pass workspaceNodeId.

The node picker and the member picker open as host dialogs. Each one returns the selection, or no selection when you cancel. See Host dialogs and OData writes.

On a Module Suite Runtime portal page, the widgets load from the page's Content Server connection, which needs a support path. Where the host supplies no Smart View widgets, such as the builder preview, the widget shows the text "Smart View widgets require Content Server Smart View", and the pickers reject with that message.

Localized strings

A Shell Component owns its strings. The page that mounts it does not. The Component AI Builder edits those strings with the component.

The strings are stored in the component as locales.json, in the same shape an AI View uses:

{
  "defaultLocale": "en",
  "locales": {
    "en": { "orders.title": "Orders" },
    "it-IT": { "orders.title": "Ordini" }
  }
}

The component reads a string with useI18n():

const { t, locale } = useI18n()

t('orders.title')

t(key, params) returns the text for the active language, then the text for the component’s default language, then the key itself. It replaces {name} placeholders with values from params. locale is the active language. The component cannot change it.

The Shell chooses that language once, when the component mounts. It reads, from lowest priority to highest:

  • the Smart View language
  • the user locale on the page
  • a non-blank locale from the user-context endpoint

Matching ignores case and treats _ and - as the same character. The Shell tries the exact code, the base language, another locale with that base language, and then the default language. A change of Smart View language reloads the page, and the Shell resolves the language again on the next mount.

flowchart TD
    SV["Smart View language"] --> Pick["Requested language (highest priority wins)"]
    PageLocale["User locale on the page"] --> Pick
    Endpoint["Non-blank locale from user-context endpoint"] --> Pick
    Pick --> Exact{"Exact code in catalog?"}
    Exact -- Yes --> Active["Active language"]
    Exact -- No --> Base{"Base language in catalog?"}
    Base -- Yes --> Active
    Base -- No --> SameBase{"Other locale with same base?"}
    SameBase -- Yes --> Active
    SameBase -- No --> DefaultLang["Default language"]
    DefaultLang --> Active
    Active --> Lookup["t(key): active text, then default text, then the key"]

The three sources feed one requested language, with the user-context endpoint taking precedence. The Shell then matches that language against the catalog, and t falls back per key.

A literal key with no text in the default language fails validation. That failure blocks compile and blocks deploy. An unused key produces a warning. Keys built at run time are not checked. After you change a translation, compile the component again and deploy it. Deploy publishes the compiled strings. A new version is stored only on deploy.

Note

t has no plural forms. Format dates and numbers with formatDate and formatNumber, which useI18n() also returns. Right-to-left layout is not handled.

A Shell-mounted AI View uses the same language resolution.

Design Profile

A Shell Component can use a Design Profile. The profile is the visual language of the generated component. Three profiles ship with the runtime:

  • jato — the Smart View look, and the default on Content Server
  • studio — a neutral, high-contrast look
  • portal — follows the portal Appearance; on Content Server it follows the Smart View look

The builder resolves the profile in this order:

  1. A profile you name in the generation request
  2. The Design Profile already stored on the component
  3. The administrator default for that host

Content Server and the Module Suite Runtime (MSR) portal each have their own default. The default applies to a component that has never been pinned. The administrator defaults are the settings design.profile.content_server and design.profile.portal.

A successful generation or compile stores the resolved profile on the component. A later change of the administrator default leaves a pinned component on its stored profile.

flowchart TD
    Start["Generation or compile"] --> Named{"Profile named in the request?"}
    Named -- Yes --> Use["Resolved profile"]
    Named -- No --> Stored{"Profile stored on the component?"}
    Stored -- Yes --> Use
    Stored -- No --> HostDefault["Administrator default for the host"]
    HostDefault --> Use
    Use --> Success{"Run succeeds?"}
    Success -- Yes --> Pin["Profile stored on the component (pinned)"]
    Success -- No --> Unchanged["Stored profile unchanged"]

The resolution is done by the builder, not by the AI. A successful run pins the result, which is why a later change of the administrator default affects only components that were never pinned.

To name a profile in the request, put its name next to a cue word, for example "studio design profile". Styling you name in the generation request applies to the elements you name.

Generated components use the profile through ds-* utilities such as bg-ds-surface. The profile values are compiled into the component bundle, so two components with different profiles can share one page.

A profile name the generation introduces that is not a shipped profile stops the run. A profile name the component already carries that is no longer shipped produces a warning, and the component compiles with the host default.

jato and studio keep a fixed palette. A portal component that uses either profile produces a validation warning. The portal profile follows the portal Appearance and uses the Smart View look on Content Server.

What the builder can read

The Component AI Builder can read the runtime source and the Module Suite Runtime REST catalog.

The catalog describes endpoints a Shell Component may call: the endpoint, its parameters, and the shape of the response. The builder uses that description while it generates the calls. The running component performs the calls.

The catalog is input for generation only. It does not bind data at run time.

Debug panel and Shell trace

While you build a page, you can open a debug panel and export a Shell trace.

The panel is off until you enable it. On a Content Server Shell Tile, the tile checks, in order:

  • the amshelldebug query parameter: 1 or true turns the panel on, and 0 or false turns it off
  • the browser value amshell.debug set to 1
  • the tile option, which is off by default

When several Shell Tiles share a perspective, the tile that starts the Shell decides. On the Module Suite Runtime portal, the portal's own configuration turns debug on instead. The default shortcut is Ctrl+Alt+Shift+D.

flowchart TD
    Load["Page loads"] --> Query{"amshelldebug query parameter?"}
    Query -- "1 or true" --> On["Debug enabled"]
    Query -- "0 or false" --> Off["Debug disabled"]
    Query -- "Not set" --> Browser{"Browser value amshell.debug is 1?"}
    Browser -- Yes --> On
    Browser -- No --> TileOpt{"Tile option on?"}
    TileOpt -- Yes --> On
    TileOpt -- No --> Off
    On --> Panel["Ctrl+Alt+Shift+D opens the panel"]
    Panel --> Scenario["Start a trace scenario"]
    Scenario --> Reproduce["Reproduce the behavior"]
    Reproduce --> Stop["Stop the scenario"]
    Stop --> Export["Export the Shell trace (Markdown)"]
    Off --> Nothing["inspect() returns null, nothing is recorded"]

The first source that gives a value decides, so the query parameter overrides the browser value and the tile option. Once debug is enabled, you delimit a scenario in the panel and export it as a trace.

Any user can enable the panel in their own browser. It shows only that user's data and allows only what browser developer tools already allow. The panel runs in its own shadow root, so its styles and the page styles do not affect each other.

The panel shows regions, the mounted components, shared page state, events, and REST and fetch traffic. You can edit shared page state, including a read-only key when you force the edit, reset keys to their initial values, publish events, and update, remount, swap, or clear a region. Those edits stay in memory. Allow lists, permissions, immutability, loop protection, property validation, and the dirty check still apply.

A region edit from the panel acts as a trusted request from outside the page, so allowSelfReplacement and allowedSourceRegions do not apply to it. Remount unmounts and mounts the component once. A property edit is merged over the live properties, so callback properties that cannot be written as JSON survive. A reset restores the declared initial value, even after a component changed the value in place.

With debug enabled, window.__AM_SHELL__.inspect() in the browser console returns the inspector. Its getSnapshot() and getEntries() return the same snapshots and recorded entries as the panel. With debug disabled, inspect() returns null, and the page does not record a trace.

A Shell trace is a Markdown export of one debug session. It contains a legend, a page snapshot, and a flat log. You start and stop a trace scenario in the panel. The boot entries are always included. Values appear as a compact type shape plus a short sample. You can include fuller values when you need them.

Synchronous work is linked to its cause exactly: an event to its handlers, a state change to its subscribers, a user action to the handlers in the same turn, a request to its response, and a region change request to its result. Work that continues after an await is linked as an inferred cause. In the log, <- marks an exact link and <~ marks an inferred one.

The trace records:

  • every REST call a Shell Component makes through the Shell
  • other fetch traffic on the page, including the load, submit, and data sources of a mounted AI View
  • clicks, submits, changes, and Enter inside regions, described by element only

It reads at most 20 KB of a text or JSON body and skips event streams and binary bodies. Typed input values are never read. XHR requests made outside the Shell REST client are not captured.

Keys that match ticket, authorization, password, passwd, secret, token, cookie, or session are redacted. HTTP headers other than content type and content length are omitted. The trace can still contain business data. Review it before you share it.

Tip

A Shell trace records behavior, not findings. Attach it together with the component sources when you ask a coding assistant to explain a problem.

Removing a component

When a Shell Component is removed, its markup and its styles are removed with it. Any Smart View widget it embedded is destroyed at the same time.

A component is unmounted when its region is replaced or cleared, and when the region element leaves the page. Shared page state is not removed with the component, so the next component in that region, or any other component, still reads it. Event subscriptions are not removed either: call the function that subscribe returns when the component unmounts, for example in onUnmounted. The debug panel marks a leftover subscriber as stale.

Host dialogs and OData writes

A host dialog opens above a modal overlay.

The node picker opens with useCsuiNodePicker and the member picker with useCsuiMemberPicker. Each call returns a promise:

Outcome Result
The user confirms Plain JSON: nodes for the node picker, users or groups for the member picker
The user cancels null
The dialog fails The promise rejects with a message

Smart View renders the dialog on the page body, so nothing is mounted inside your component. A single member selection is returned in the same form as a list. The pickers need the hostWidget capability, the same one that embeds Smart View widgets.

Treat cancel as a normal result, not an error.

Warning

Check a picked id again on the server before you use it in a write.

An OData write accepts the parameters documented for that write.

Module Suite Runtime and a mounted AI View

The Module Suite Runtime portal hosts Shell Pages and Shell Components. A deploy from the Component AI Builder on Module Suite Runtime stores the new component.

On Module Suite Runtime, the Component AI Builder does not edit pages. It can propose a region for the component, and an administrator applies that proposal to the page controller. Access to a portal page, and to the mounting of a component, is controlled by an access list of principals: anonymous, authenticated, role:X, perm:X, or user:name. An access list does not protect the component source.

An AI View mounted on a Shell Page runs its OnLoad script. The values that script sets remain after an action roundtrip.

A mounted AI View keeps its own submit behavior. It registers its dirty state with the Shell, uses the active language the Shell resolves, and can return its region to a list after submit or discard.

Validation at compile and deploy

The builder validates a component when it compiles. An error blocks compile and deploy. A warning is listed and does not block.

Condition Result
A literal string key has no text in the default language Error. The builder tries to repair it. Compile and deploy are blocked.
A string key is not used Warning
A string key is built at run time Not checked
A translation changed after the last compile The bundle is stale. Compile again before you deploy.
The generation introduces a profile name that is not shipped Error. The run stops.
The stored profile is no longer shipped Warning. The component compiles with the host default.
A portal component uses jato or studio Warning

Troubleshooting

Symptom Check
Nothing mounts The perspective needs a tile that hosts the Shell Page. Contributed regions alone mount nothing.
A component is missing for one user The registry leaves out components the user cannot access on the component node.
A component call throws The capability for that API was not authorized for the component.
Mounted components disappear after an action The layout performed a roundtrip that replaced its body. Move that widget to its own tile.
Regions return to their first component Reload and browser back reset regions to their initial components.
A click in a nodes table leaves the Shell Page Smart View default actions apply inside embedded widgets.
Related workspaces is empty The perspective is not a business workspace perspective, and the component passes no workspaceNodeId.
A changed translation does not appear Compile the component again, then deploy it.
A second region with the same id is ignored Region ids must be unique on the page. The engine keeps one.
The debug panel does not open amshelldebug=0 or false in the URL turns the panel off, whatever the other settings say.