Skip to content

Command Center Forms

When an AppComponent widget needs a specialized input experience, the right object to return is EditableFormDefinition.

This is the form contract used when the default argument resolution is not enough.

Use it when you need things like:

  • grouped sections
  • domain-specific labels
  • richer field kinds such as percent
  • explicit defaults
  • custom field tokens for downstream binding or draft state

When You Do Not Need A Custom Form

For simple function arguments, Command Center can resolve the default form directly from the operation contract.

That means if your endpoint or function only exposes normal flat arguments with a straightforward shape, you usually do not need to add a custom form.

Typical examples:

  • a few scalar query parameters
  • a small request body with standard primitive fields
  • simple enums, booleans, dates, and numbers

In those cases, let Command Center generate the form automatically.

When You Should Return EditableFormDefinition

Use EditableFormDefinition when you want to control how the form is presented, especially for AppComponent widgets that should render a specialized form with grouped sections and explicit field definitions.

This is the right path when:

  • the autogenerated form is too flat
  • you want a product-specific language instead of raw parameter names
  • you need sectioned inputs
  • you want more control over editable vs read-only fields
  • you want percent or formatter hints
  • you need stable field tokens for app-specific behavior

The Core Objects

The SDK models are:

  • EditableFormDefinition
  • FormSectionDefinition
  • FormFieldDefinition
  • FormFieldKind

These live in:

  • mainsequence.client.command_center

The important mental model is:

  • one form
  • one or more sections
  • each section contains fields
  • each field has a stable token, a user-facing label, a kind, and editability rules

Example

from fastapi import APIRouter

from mainsequence.client.command_center import (
    EditableFormDefinition,
    FormFieldDefinition,
    FormFieldKind,
    FormSectionDefinition,
)

router = APIRouter()


@router.get(
    "/pricing-context/{product_id}",
    response_model=EditableFormDefinition,
)
def get_pricing_context_form(product_id: str) -> EditableFormDefinition:
    return EditableFormDefinition(
        form_id=f"pricing-context:{product_id}:v1",
        title="NOTE",
        description="custom note based on DoubleNoTouchMXN with additional funding parameter.",
        sections=[
            FormSectionDefinition(
                id="custom_fields",
                title="Custom Fields",
                fields=[
                    FormFieldDefinition(
                        token="custom::funding_rate",
                        name="funding_rate",
                        label="Funding Rate",
                        kind=FormFieldKind.PERCENT,
                        editable=True,
                        required=True,
                        value=None,
                        default_value=None,
                        formatter="percent",
                    ),
                    FormFieldDefinition(
                        token="custom::notional",
                        name="notional",
                        label="Notional",
                        kind=FormFieldKind.NUMBER,
                        editable=True,
                        required=False,
                        value=100,
                        default_value=100,
                    ),
                ],
            )
        ],
    )

What This Example Is Doing

  • form_id gives the form a stable identity for reset/reload semantics
  • title and description control the form header
  • sections let you group fields into meaningful blocks
  • token is the stable field identity used for draft state and downstream bindings
  • name is the backend field identifier
  • label is what the user sees
  • kind tells Command Center how to render the field
  • formatter gives the frontend an extra presentation hint

Why token Matters

The token field is not cosmetic.

It is the stable global identifier for the field in the Command Center interaction model. That is what makes it useful for:

  • draft state
  • bindings
  • app-specific coordination

If you are defining a custom form, keep tokens stable across compatible versions.

Choosing The Right FormFieldKind

The built-in field kinds are:

  • STRING
  • NUMBER
  • INTEGER
  • BOOLEAN
  • DATE
  • DATETIME
  • PERCENT
  • ENUM
  • JSON

Use the kind that reflects the business meaning, not just the raw transport type.

Example:

  • PERCENT is better than NUMBER for a funding rate if the UI should treat it as a percentage input

A Practical Rule

Start with the default generated form.

Only introduce EditableFormDefinition when you actually need:

  • sectioned layout
  • specialized labels
  • richer formatting
  • explicit token control

That keeps the Command Center integration simpler and makes the custom form a deliberate choice instead of a default habit.

Relationship To AppComponent Widgets

This page is specifically relevant for AppComponent widgets.

The usual pattern is:

  1. the widget resolves an operation contract
  2. Command Center can often build the form automatically from that contract
  3. if that is not enough, the app returns EditableFormDefinition

So the custom form is an override for richer UX, not a requirement for every AppComponent.

It is also important not to mix this with widget output contracts.

  • EditableFormDefinition describes how the widget should collect input
  • the models in mainsequence.client.command_center.data_models describe the exact output shape expected by some widgets when your API feeds them directly

If your API is powering a widget end-to-end, you may use both:

  • a form contract for the widget input
  • a widget data contract for the widget output