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 → ScreenContractResponse 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
| Property | Type | Default | Purpose |
|---|---|---|---|
columns | array | required | Column definitions |
rows | array | [] | Data rows |
row_count | integer | 0 | Total 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_size | integer | 25 | Display hint: how many rows fit on screen |
current_page | integer | 0 | Always 0 (no server-side pagination) |
selectable | boolean | false | Whether rows can be selected with Enter |
editable | boolean | false | Whether cells can be edited (for HeaderLines and Grid screens) |
on_select | string | null | URL template for row selection |
table_align | string | null | Horizontal 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_column | string | null | For lookup lists only: which column's value is returned |
autofill | object | {} | For lookup lists only: column ID → field ID mapping |
on_drill | string | null | URL template for Ctrl+Enter drill-down into a row's detail view. Same {0} substitution as on_select. |
Column definitions
| Property | Type | Default | Purpose |
|---|---|---|---|
id | string | required | Column identifier |
label | string | required | Column header text |
type | string | "Text" | Data type. Affects alignment and editing behavior. |
width | integer or "fill" | 10 | Fixed character width, or "fill" for remaining space |
align | string | "left" | Text alignment: "left", "right", "center" |
editable | boolean | false | Whether cells in this column can be edited |
options | array | null | Fixed options for Option-type columns |
lookup | object | null | Lookup binding for this column |
validation | object | null | Validation rules for cells in this column |
quick_entry | boolean | true | When false, Enter skips this column. Tab still visits all. |
formula | string | null | Arithmetic 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 charactersIf 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"
]
}| Property | Type | Purpose |
|---|---|---|
index | integer | Row index (used for selection tracking) |
values | array | Column 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=cannonYour 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.
Navigation lists vs lookup lists
The same TableSpec structure is used for both navigation lists (Customer List) and lookup lists (Post Code Lookup). The difference:
| Property | Navigation list | Lookup list |
|---|---|---|
on_select | URL template | absent |
value_column | absent | column ID |
autofill | absent | column→field mapping |
on_drill | optional URL template | optional URL template |
| Enter behavior | Client fetches the card URL | Client returns value to the originating field |
| Ctrl+Enter behavior | Opens on_drill card | Opens on_drill card (Esc returns to lookup) |
See Lookups for lookup list details.