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
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.