This page describes the module shell pattern: navigation and frame components live in one surface, while route content is rendered into a named SurfaceHost.
Structure¶
A module navigation shell has two surfaces:
- Shell surface: sidebar, navigation tree, splitters, scroll wrappers, and
SurfaceHost. - Content surface: the route-specific UI rendered into the host.
The shell is responsible for layout. The content surface is responsible for page content.
Shell Surface¶
Build the shell with concrete layout components. The common shape is:
RoworSplitteras the root frame.SidebarorColumnfor navigation.ScrollAreaaround the content region when the hosted content should scroll.SurfaceHostwith a stablesurface_idfor the content surface.
builder = sdk.ui.Builder()
builder.add(sdk.ui.TreeView("module_nav", nodes=nodes, click_action="nav"))
sidebar = sdk.ui.Sidebar("module_sidebar", ["module_nav"])
sidebar.set_property("width", 260)
sidebar.set_property("max_width", 360)
builder.add(sidebar)
host = sdk.ui.SurfaceHost("module_content_host", surface_id="module_content")
builder.add(host)
content_scroll = sdk.ui.ScrollArea("module_content_scroll", [host.id])
content_scroll.set_property("stretch", True)
content_scroll.set_property("transparent", True)
builder.add(content_scroll)
shell = sdk.ui.Row("module_shell", [sidebar.id, content_scroll.id])
shell.set_property("align", "fill")
shell.set_property("stretch", True)
builder.add(shell)- kind: Row
id: module_shell
align: fill
stretch: true
children:
- kind: Sidebar
id: module_sidebar
width: 260
max_width: 360
children:
- module_nav
- kind: ScrollArea
id: module_content_scroll
stretch: true
transparent: true
children:
- kind: SurfaceHost
id: module_content_host
surface_id: module_contentContent Surface¶
Every content route that should appear inside the shell must render to the same named surface used by SurfaceHost.
async def render(params: dict, session: dict):
builder = sdk.ui.Builder()
builder.set_surface("module_content", shell_route="/module/shell")
builder.add(sdk.ui.Title("page_title", "Overview", level=2))
builder.add(sdk.ui.Column("page_root", ["page_title"]))
return buildershell_route tells the runtime which shell route owns the host for this surface.
SDK Helpers¶
Use prepare_shell_surface(...) in content routes when you want the SDK to set the target surface and create an empty content container:
content_id = sdk.ui.prepare_shell_surface(
builder,
surface_id="module_content",
shell_route="/module/shell",
content_component_id="module_content_root",
)Use mount_shell_frame(...) in the shell route when the module follows the common navigation/content pattern:
root_id, host_id = sdk.ui.mount_shell_frame(
builder,
nav_component_id="module_nav",
content_surface_id="module_content",
root_id="module_root",
splitter_id="module_splitter",
nav_container_id="module_nav_container",
content_scroll_id="module_content_scroll",
content_host_id="module_content_host",
splitter_sizes=[260, 860],
splitter_max_sizes=[360, 0],
)The helper builds:
- root
Row Splitter- navigation
Column - content
ScrollArea SurfaceHost
Navigation¶
Navigation components should change route, not replace the hosted content manually. For a tree or list navigation item, use click_action="nav" and route paths that render into the content surface.
builder.add(
sdk.ui.TreeView(
"module_nav",
nodes=[
{"id": "overview", "label": "Overview", "path": "/module"},
{"id": "settings", "label": "Settings", "path": "/module/settings"},
],
click_action="nav",
click_mode="single",
selection_mode="single",
active_as_selection=True,
)
)Use sdk.ui.nav_active_path_rule(path) for active navigation state when the navigation component supports it.
Responsibilities¶
Shell surface:
- navigation layout
- sidebar or split sizing
- scroll container around the host
SurfaceHost(surface_id=...)
Content surface:
- page-specific titles, forms, tables, charts, and actions
- data model for that surface
- updates targeting its own
surface_id
Notes¶
- Keep
surface_idstable. Changing the content route should render a new content surface update, not mutate the host id. - Do not put module content directly in the shell when it is route-specific.
- Do not use
SurfaceHostas a generic container. It is a mount point for another surface. - Put
ScrollAreaaround the host when the hosted content must scroll inside the shell frame.