The problem
A review form has three tabs — line items, charges, and a panel of reviewer instructions tucked at the bottom. A reviewer who only ever looks at the first tab can still click Approve: the document had no open exceptions, the button was enabled, and the task transitions toapproved. Nobody actually read the charges, and the instruction panel that explained how to handle a multi-stop shipment route was never even expanded.
The gate closes that hole. The reviewer cannot finish until they have visited the data you flagged as mandatory.
The pattern
A single action property turns the gate on:
It lives under
metadata.actions[].properties in the task template YAML, alongside the existing exception gates. The gate is evaluated entirely in the UI whenever a requirement is satisfied — there is no round trip to the server.
When the gate blocks an action, an amber info icon appears next to the button. Clicking (or keyboard-focusing) it opens a popover headed Before continuing: that lists the outstanding items as bullets. It shows up to six items; if more remain it appends a …and N more line.
What counts as outstanding
The gate collects requirements from two places: the layout components on the form, and the open data exceptions on the paths the form binds.Opting fields in
The mandatory requirements come from props on the layout components themselves, not from the task template. Add them where the field is rendered in the data form schema. See Layout Components for the full prop tables — the gate-specific props are:mustExpand only has an effect on a collapsible panel. Set collapsible: true on the same v2:panel or the requirement is dropped and a warning is logged in dev.Exception scoping
The non-exception requirements (mustView, mustExpand, mustScroll) always apply to a gatedByCompleteness action. Exception-source requirements are scoped:
- If the action also declares
onlyEnabledIfNoOpenExceptionsForPaths, only exceptions on those listed paths fold into the gate. (See Gating actions on exceptions.) - Otherwise, every open exception on a path the form binds applies.
tagPath / groupTaxon values of every component it renders. Exceptions on paths the form does not render never fold into its gate, and matching is hierarchical — an exception on a child attribute is in scope for a panel bound at its parent group. An exception blocks the gate while it is open and has no closing comment; resolving it (or adding a closing comment) clears it from the gate.
Example
An invoice review form with two tabs after the first, a collapsible instruction panel the reviewer must open, and anApprove action gated on completeness:
Review Line Items tab, Review Charges tab, Expand Reviewer Instructions, and one bullet per outstanding exception.
The gate is in-memory and lives for one form view. Reloading the form starts the reviewer over — every tab, panel, and scroll requirement is outstanding again. Keep the list short enough that a re-read is not punishing.
Recipe summary
- On each layout component, opt the mandatory fields in:
mustViewon av2:tabs,mustExpand(withcollapsible: true) and/ormustScrollon av2:panel. - On the action in the task template, set
gatedByCompleteness: true. - To restrict which exceptions count, add
onlyEnabledIfNoOpenExceptionsForPathsto the same action. - Keep a reject or escape action ungated so a reviewer is never trapped.
- Test by leaving one flagged tab unopened — the action must stay disabled and the popover must name it.
