SaaS.Dev

Verb inputs: what a form needs

Why a Verb that takes only its record gets a working button, and why a Verb with typed inputs gets a button but no form in the Admin.

A Verb declared with {} takes only its record, so a button can run it. A Verb with typed inputs needs a form to collect them, and the Admin draws none.

What a Verb declares

Each key under verbs in a Noun file is a Verb, and its value maps each input to a type. {} declares a Verb that takes only the record. The Noun file of leaseclock.com's Lease declares two such Verbs:

verbs:
  renew: {}
  terminate: {}

A Verb may instead take inputs, typed like properties, with ? marking an optional one. This variant of renew takes a term and an optional increase:

verbs:
  renew: { months: integer, increase?: number }

Each Verb compiles to one input type, such as LeaseRenewInput. Every Interface form of the Verb takes it, such as $.Lease.renew({ lease, months: 12 }) in the SDK. The Function behind the Verb takes it as its second argument, as Verb functions shows.

What the Admin draws for a Verb

Every Verb beyond create, get, list, update and delete becomes one button, labeled from its name. The button runs the Verb on the record it sits on and sends the record alone.

VerbButtonWhereRuns withRule
Lease.renewReneweach list row, the record page, the edit formthe record, no inputsverb-action
Lease.terminateTerminateeach list row, the record page, the edit formthe record, no inputsverb-action

A completed call shows a notice and reloads the record (verb-result). A refused call shows the Verb's reason and leaves the screen as it was (refusal-message).

Why typed inputs get a button and no form

The Admin builds its screens from the record's properties, and its buttons from the Verb names. It never reads a Verb's input types, so it has no fields to put in a form.

With the renew variant above, Renew still appears on every row. A click sends the record and an empty input, so months never reaches the Function. The Verb then runs without its term, or its rules refuse the call.

The Verb declaresIn the AdminThe Function receives
renew: {}a Renew button that worksthe record
renew: { months?: integer }a Renew buttonthe record, and no months
renew: { months: integer }a Renew buttonthe record, without the required months

clinicshift.app shows the split on one Noun. Its Swap.approve takes only the swap, so its button works. A decline that asks for a reason gets a button that never sends one:

verbs:
  approve: {}
  decline: { reason: text }

Shape Verbs for the screens that run them

A Verb that people run from the Admin works best taking only its record. A value the Verb needs can live on the record instead, where the edit form sets it. leaseclock.com's renew: {} extends a lease by one year, so its button needs nothing more.

Inputs stay right for Verbs called through the API, SDK, CLI and MCP server. Each of those carries the inputs in the call, typed by the definition.

PlanA form for every Verb with inputs

In the design, a Verb with typed inputs opens a form with one control per input, drawn by the same rules as Field kinds and their controls. An input with no ? is required, and the form sends the record with the inputs. Renew then asks for months, and Decline asks for a reason in a multi-line box.

Its rule, action-form-from-inputs, waits: the Admin reads its definition from a format with no input types. It needs the projection to read Noun files, where the inputs already live.

Contents