Skip to content

Predicates

Conditional presentation — this field is editable only while…, visible unless…, valid when… — is the spine of model-driven UI. In declui a predicate is a string in a small, whitelisted expression grammar, written on the field that owns it. Where you declare it decides where it runs.

The vocabulary

A predicate may reference:

Name Meaning
value this field's own value
any sibling field name the form owns every field as a signal, so cross-field conditions are ordinary
editing the view/edit mode flag (see below)
len(x) abs(x) round(x[, n]) min(a, b, …) max(a, b, …) today() the whitelisted function set (with their arities)

That's all. The compiler parses the string, walks the whole AST against a node whitelist (boolean ops, comparisons, not, whitelisted calls, names, literals — nothing else), validates every identifier, and fails closed on anything outside the grammar. Because the walk covers a complete parse, there is no unscanned corner: three independent adversarial security reviews — the last one probing 21 hostile predicates (__import__, a __class__/__subclasses__ walk, lambda, comprehensions, f-strings, eval, globals, method calls, an or alert(1) injection) — found zero escapes. Model-supplied text (labels, options, messages) is additionally entity-encoded against template injection.

Whitelisting the name is not enough, so the call itself is checked: len(value, 2) is rejected (wrong arity), as is round(value, places) (the precision must be an integer literal — it compiles to a fixed factor). A keyword argument is rejected outright.

Where each predicate lands

Declared on Compiles to Runs
Field(visible="…") a :show binder in the browser, re-evaluated on any dependency change
Field(editable="…") :attr="disabled: not (…)" in the browser
Field(valid="…") an error message :show, gated on the field being non-empty in the browser
Screen(object_editable="…") AND-ed into every field's disabled binder in the browser
@action(visible="…") a Jinja {% if %} around the row's button on the server, per row at render

Field predicates are client binders — the cross-field reactivity that classic metadata-driven frameworks bought with a server round trip per keystroke is a compiled expression here, zero network. The action gate is the deliberate exception: it governs a server command over server-held rows, so it renders (and is enforced) where the rows live.

Examples

@dataclass
class User:
    rating: Annotated[int, Field(widget="RatingBar")] = 0
    bio:    Annotated[str, Field(widget="RichText",
                                 editable="rating > 50")]          # cross-field
    password: Annotated[str, Field(secret=True,
                                   valid="len(value) > 5 or 'Too short'")]
    suspended: Annotated[bool, Field()] = False

USER = Screen(model=User, object_editable="not suspended")
  • Drag rating past 50 and bio unlocks, live.
  • Tick suspended and every field locks — one screen-wide rule. The field a whole-object rule reads is automatically exempted (otherwise you could never un-suspend).
  • valid= uses the truthy-or-message idiom: the expression is valid when truthy; the string after or is the error message shown. An empty, untouched field never nags — the error is gated on value != '' (declui has no required-validation, so empty is valid by definition; valid is a format check).

The editing mode

If any predicate references editing, the generated form gains an editing: Local[bool] = True signal and an Edit/Done toggle button. visible="editing or value != ''" reproduces the classic "hide the empty field in view mode" pattern.

Known limits (each fails closed)

  • Relational comparison (< / >) on a Decimal field is rejected — Decimals are string-valued signals and the compare would be lexical. Equality is fine.
  • An @action(visible=…) gate may not call functions, and may not reference date/datetime fields (the server row holds a date object, while the client grammar assumes an ISO string).
  • A predicate referencing a field not rendered on that screen errors, naming the field.
  • A whitelisted function called with the wrong arity — or round(x, n) with a non-literal n — errors on the Field that wrote it.