<a id="forms"></a>

# Forms and relationships

Use a GraphQL form when its transaction maps directly to one mutation. The compiler
reads the mutation's input types and supported validation directives, derives the
field contracts, and verifies paths and requiredness. The form supplies variables
with those exact paths. Lua is not required for this case.

## Scalars a form can edit

Built-in GraphQL String, ID, Boolean, Int and Float inputs have known form shapes.
A custom scalar must expose `@iac_scalar(carries:)` in the instance schema. The supported carries values are STRING, NUMBER, BOOLEAN, JSON_OBJECT,
JSON_ARRAY and JSON; they describe its wire value, not a replacement for server
validation. Business ID scalars, CurrencyISO and ISO8601 carry strings in the
current server schema. Enum choices are derived from the enum definition.

An unannotated custom scalar fails with `Custom scalar <name> needs
@iac_scalar(carries:) for forms`. The diagnostic identifies the selected input
path and its XML field. Check the scalar's declaration in your instance schema
instead of assuming every custom scalar is a string. If a scalar you need lacks
this metadata, contact CoCoCo support rather than relabelling it to bypass
validation.

```xml
<workspace id="customer-create" title="Create customer" role="Customer service" xmlns:t="urn:cococo:dataxml">
  <panel id="main" title="Customer">
    <query type="graphql">query { __typename }</query>
    <form id="customer" mode="create">
      <submit type="graphql" errors="createCustomer.errors">
        <![CDATA[mutation($input:CreateCustomerInput!){createCustomer(input:$input){errors{path message}}}]]>
      </submit>
      <field path="input.name" label="Customer name"/>
      <field path="input.email" label="Email" control="email"/>
      <form-actions save-label="Create customer"/>
    </form>
  </panel>
</workspace>
```

For editing, identify the record and its identity field, then map each editable
input to its record-relative source. Hidden values can supply IDs. Changed-only
fields are useful for patches, but a required input cannot be made optional merely
by changing its submission policy. Omission and explicit null are different;
clearing must be allowed by the server contract.

A field or hidden value with `state` reads declared Workspace state into the
form input. Editing the form changes its draft, not that Workspace state. Use a
`filter` to edit query state and refetch; its Apply action does not submit a form.

Select fields by declaring the fields the author should edit. The compiler checks
that this selection can still supply all required inputs. Use the record-editor
composite when selection, loading, identity and editing form one workflow.

Relationship fields can use static options or a lookup backed by another panel.
A record picker stores its declared value, while displaying its declared label.
The lookup `items` path is relative to its source panel result; `value` and `label`
are paths relative to each item, without an item alias. For connection edges,
`node.id` and `node.name` are typical item paths when those fields are selected.
A root-level `data-source` can supply these options without a visible panel.
It requires an `id` and one query, accepts an optional title and `wait-for`, and
participates in ordinary loading and refresh. Point the lookup's `panel` attribute
to that ID. It cannot contain display blocks. This removes visual clutter; it does
not hide the returned data from an authorized Workspace reader.
Loading options is a UI convenience, not authorization to reference every record.
The server validates the relationship and current user's permissions on save.

The session retains drafts, touched state and validation errors. It prevents
simultaneous duplicate saves, protects later edits from older responses, and
separates unsaved work from the current submittable projection. Reset is an explicit
discard operation and is disabled while submission is in flight.

Bind the mutation's error collection and, when present, its canonical result.
Map server errors to input paths. Canonical fields acknowledge only the submitted
transaction; a missing submitted field is not a successful canonical save.
For compound business operations use a Lua action with the same form lifecycle,
as shown in [server Lua](workspace-manual://workspace-xml-4/server-lua).

## Confirmation and refresh

After validating the draft, the CoCoCo app captures the submission and asks the
user to confirm it. The optional submit `confirm` attribute supplies the
message. When omitted, the message is the workspace title followed by
`: confirm change`. Whether a dialog appears depends on the app's confirmation
settings; omitting the attribute does not disable confirmation.

Cancelling sends no mutation and retains the draft for editing. If confirmation
fails, the form returns to a recoverable error state. Edits made while confirmation is
open do not change the captured submission. After a successful domain response,
the engine acknowledges the submitted values and refreshes panels. Validation,
transport and business errors do not trigger that success refresh.

The app's and each panel's Refresh controls explicitly reload query data even when arguments
have not changed. Panels waiting for selection remain idle. Refreshing does not
silently discard a dirty draft: a changed record is offered as a pending update
with the existing keep-editing/discard choice. XML does not currently configure
polling or subscription topics; do not invent a panel `refresh` attribute.