Skip to content

Lists ​

Summary ​

A List is a full-screen scrollable table for browsing records. Customer List, Item List, Ledger Entries. This page explains how to build List ScreenContracts.

What the client does with a List ​

The client renders a table with column headers, scrollable rows, and type-to-search filtering. The user navigates with arrow keys and presses Enter to open a record.

┌─ Customers ──────────────────────────────────────────────┐
│ No.       Name                          Balance          │
│ ───────── ───────────────────────────── ──────────────── │
│ 10000     The Cannon Group              45,000.00        │
│ 20000     Selangorian Ltd.              12,500.00        │
│ 30000     John Haddock Insurance        0.00             │
└──────────────────────────────────────────────────────────┘

Building a List ​

Route ​

GET /screen/customer_list → ScreenContract

Response structure ​

json
{
  "layout": "List",
  "title": "Customers",
  "lines": {
    "columns": [
      {
        "id": "no",
        "label": "No.",
        "type": "Text",
        "width": 10
      },
      {
        "id": "name",
        "label": "Name",
        "type": "Text",
        "width": "fill"
      },
      {
        "id": "balance",
        "label": "Balance",
        "type": "Decimal",
        "width": 15,
        "align": "right"
      }
    ],
    "rows": [
      {
        "index": 0,
        "values": [
          "10000",
          "The Cannon Group",
          "45,000.00"
        ]
      },
      {
        "index": 1,
        "values": [
          "20000",
          "Selangorian Ltd.",
          "12,500.00"
        ]
      },
      {
        "index": 2,
        "values": [
          "30000",
          "John Haddock Insurance",
          "0.00"
        ]
      }
    ],
    "row_count": 3,
    "selectable": true,
    "on_select": "/screen/customer_card/{0}"
  }
}

TableSpec properties ​

PropertyTypeDefaultPurpose
columnsarrayrequiredColumn definitions
rowsarray[]Data rows
row_countinteger0Total record count before filtering. Use this to display "312 records" even when ?query= limits the returned rows. When no filtering is active, set it to rows.length.
page_sizeinteger25Display hint: how many rows fit on screen
current_pageinteger0Always 0 (no server-side pagination)
selectablebooleanfalseWhether rows can be selected with Enter. On Grid and HeaderLines screens, also enables row insertion (Ctrl+N) and deletion (Ctrl+D).
editablebooleanfalseWhether cells can be edited (for HeaderLines and Grid screens)
on_selectstringnullURL template for row selection
table_alignstringnullHorizontal alignment of the table within the terminal: "left", "center", "right". Useful for narrow tables (e.g., modal lookups) that don't fill the screen. If null, the table is left-aligned.
value_columnstringnullFor lookup lists only: which column's value is returned
autofillobject{}For lookup lists only: column ID → field ID mapping
on_drillstringnullURL template for Ctrl+Enter drill-down into a row's detail view. Same {0} substitution as on_select.

Column definitions ​

PropertyTypeDefaultPurpose
idstringrequiredColumn identifier
labelstringrequiredColumn header text
typestring"Text"Data type. Affects alignment and editing behavior.
widthinteger or "fill"10Fixed character width, or "fill" for remaining space
alignstring"left"Text alignment: "left", "right", "center"
editablebooleanfalseWhether cells in this column can be edited
optionsarraynullFixed options for Option-type columns
lookupobjectnullLookup binding for this column
validationobjectnullValidation rules for cells in this column
quick_entrybooleantrueWhen false, Enter skips this column. Tab still visits all.
formulastringnullArithmetic expression evaluated client-side after each edit.

The "fill" width ​

The width property accepts either an integer (fixed character width) or the string "fill" (expand to remaining space). In typed languages like Rust, Go, or TypeScript, deserialize this as a union type or tagged enum — it is intentionally mixed.

One column per table should use "fill" as its width. This column expands to take all remaining space after fixed-width columns are allocated.

Terminal: 80 characters wide
Columns: No. (10) + Name (fill) + Balance (15)
Fixed: 10 + 15 = 25
Name gets: 80 - 25 - borders ≈ 50 characters

If no column has "fill", the last column gets remaining space. If multiple columns have "fill", they share remaining space equally.

Numeric types (Decimal, Integer) default to right-alignment. You can override with align.

Rows ​

json
{
  "index": 0,
  "values": [
    "10000",
    "The Cannon Group",
    "45,000.00"
  ]
}
PropertyTypePurpose
indexintegerRow index (used for selection tracking)
valuesarrayColumn values in order. All values are strings. Your server pre-formats everything.

Row selection (on_select) ​

When the user presses Enter on a row, the client fetches the on_select URL template:

json
{
  "on_select": "/screen/customer_card/{0}"
}

The {N} placeholder is replaced by the value of column at index N from the selected row. {0} is the first column, {1} the second, and so on.

For example, if the user selects a row with values ["10000", "The Cannon Group", "45,000.00"], the client fetches:

GET /screen/customer_card/10000   ← {0} = "10000"

For compound keys, you can combine multiple columns:

json
{
  "on_select": "/screen/ledger_entry/{0}/{1}"
}

→ GET /screen/ledger_entry/10000/42

If a referenced index is out of range, it is substituted as an empty string.

Row drill-down (on_drill) ​

When the user presses Ctrl+Enter on a row, the client fetches the on_drill URL template — same substitution pattern as on_select:

json
{
  "on_select": "/screen/customer_card/{0}",
  "on_drill": "/screen/customer_card/{0}"
}

This works on both navigation lists and lookup lists. On a lookup list, it lets the user inspect a record before selecting it — Ctrl+Enter opens the card, Esc returns to the lookup, and Enter still selects.

json
{
  "value_column": "no",
  "autofill": { "name": "sell_to_name" },
  "on_drill": "/screen/customer_card/{0}"
}

If on_drill is absent, Ctrl+Enter does nothing.

Search and filtering ​

The client sends search queries via the ?query= parameter:

GET /screen/customer_list?query=cannon

Your server should filter rows based on this query and return only matching results. The client re-sends the query whenever the user types or deletes in the search input.

If no ?query= parameter is present, return all rows.

Pagination ​

The current protocol does not use server-side pagination. Your server returns all matching rows in a single response. The client scrolls through them locally.

page_size is a display hint telling the client how many rows to show at once (default: 25). row_count should equal the number of rows in the response.

For large datasets, rely on ?query= filtering to keep the response size reasonable.

The same TableSpec structure is used for both navigation lists (Customer List) and lookup lists (Post Code Lookup). The difference:

PropertyNavigation listLookup list
on_selectURL templateabsent
value_columnabsentcolumn ID
autofillabsentcolumn→field mapping
on_drilloptional URL templateoptional URL template
Enter behaviorClient fetches the card URLClient returns value to the originating field
Ctrl+Enter behaviorOpens on_drill cardOpens on_drill card (Esc returns to lookup)

See Lookups for lookup list details.