Skip to content

Screens & mounting

Screen

from situ.declui import Screen, zones

ISSUE = Screen(
    model=Issue,
    zones={
        "list":   zones(main=["subject", "status", "priority"]),
        "detail": zones(header=["subject", "status"], body=["body", "owner", "notes"]),
        "form":   zones(main=["subject", "body"]),
    },
    object_editable="not suspended",     # optional whole-object predicate
)
Parameter Meaning
model the typed class (any supported source)
zones per-screen layout — see below
object_editable a predicate AND-ed into every field's editability
rules reserved for the future rule-sheet layer; accepted today and ignored

Zones

zones(name=[fields...], ...) lays fields into named <div class="declui-zone--name"> slots, in declaration order — an explicit compile-time layout. Each zone renders a role="group" with a visible title. For a list screen, zones["list"] selects and orders the columns.

Zones are explicit

A field placed in no zone of a screen is not rendered on that screen — deliberately, so the layout is the layout. Referencing an unplaced field from a predicate gives a precise error. Unknown field names and non-identifier zone names fail closed.

mount_model and the screen modes

from situ.declui import mount_model

A form (the default)

mount_model(path="/sample", screen=SAMPLE)                    # screens=("form",)

A list

mount_model(path="/tasks", screen=TASKS, screens=("list",),
            rows=[Task(...), Task(...)])

A client-seeded :each: one read-only cell per non-hidden scalar column (booleans render Yes/No, enums by value; long text cells get hover title tooltips). Zero network after load.

Master-detail

mount_model(path="/contacts", screen=CONTACTS, screens=("list", "detail"), rows=ROWS)

A list plus a detail pane in one mount. A row click copies the row into per-field detail signals and reveals the pane — selection is entirely client-side.

The server tracker

mount_model(path="/issues", screen=ISSUE,
            service_type=IssueService,     # from your Dishka container (mount_model is the Litestar mount)
            facade=Issues,                 # wraps the service for the handlers
            context=context)               # (service, view) -> the rows the region renders

The full seam: the region is server-rendered from context() on every command; the model's async @actions become per-row command buttons; zones["detail"] adds a server-rendered detail pane that walks relationships from the object graph — a reference field shows the target's label, a list[SubModel] renders as a nested list; Field(search=True) adds a zero-network client filter over the server-rendered rows; selection survives command swaps.

The server tracker requires an id field on the model (the command key t.id).

Seeding client data

Two mount kwargs feed the pure-client modes:

  • rows=[...] — model instances for list / master-detail screens (values coerced exactly like defaults).
  • choices={"owner": [user1, user2]} — the option lists for reference fields; option value is the target's key, label its label_field.

Fail-closed mounting

mount_model refuses ambiguous configurations:

  • service_type= without context=CompileError (there would be dead buttons over an empty region).
  • A server mount passed the client kwargs screens= / rows= / choices=CompileError.
  • An unsupported screens tuple → CompileError.

template= and meta= work as on every situ mount; declui ships a default page style (declui.css) and injects the kit assets only when a kit widget is used.