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.
| Verb | Button | Where | Runs with | Rule |
|---|---|---|---|---|
Lease.renew | Renew | each list row, the record page, the edit form | the record, no inputs | verb-action |
Lease.terminate | Terminate | each list row, the record page, the edit form | the record, no inputs | verb-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 declares | In the Admin | The Function receives |
|---|---|---|
renew: {} | a Renew button that works | the record |
renew: { months?: integer } | a Renew button | the record, and no months |
renew: { months: integer } | a Renew button | the 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.
Field kinds and their controls
Every field type a Noun file takes, with its control in the Admin's list, record page and forms, and its JSON and Markdown renderings.
API only: what a Customer gets without an App
What a Customer of Headless SaaS reaches with no App: five Interfaces, the machine registers an Agent reads, keys, and prices per call.