Record screen standard

Table of Contents

1. Summary

Many journeys maintain records of one kind: currencies, desk groups, calendars, tenants, parties, roles. Every such screen has the same shape and the same behaviour. This page defines that shape once. It covers the same scope as the Qt standard that the retired entity specification recorded: the list and its paging, the record page, create, amend and delete, change reasons, history and revert, related records, icons, states, feedback, access, safety, naming and exceptions.

A journey decides which records a person works with and in what order. This standard decides how each record screen in that journey looks and behaves. Each rule names the shared component that implements it, so a screen gets the rule by using the component, not by copying it. A screen that needs to differ is an exception, named in Exceptions, and an exception changes which parts exist, never how they look.

2. Detail

2.1. Where this standard applies

A record screen is any screen, or any journey step, whose subject is the records of one kind: it lists them, opens one, and creates, amends or deletes them. The currency, desk group, currency pair and holiday calendar screens are record screens. So are the tenant, party and role screens.

These are not record screens, and this standard does not govern them:

  • RS-SCOPE-01 an Audit screen, which reads a log of events that happened, such as sign-ins or publications (see the History and Audit rule in UX Language);
  • RS-SCOPE-02 a wizard step that collects input for one operation, such as provisioning a tenant;
  • RS-SCOPE-03 a dashboard or an area page of tiles.

A journey can hold record screens and other steps together. The User Journeys rule still holds: one screen per goal, not one screen per table. A record screen can therefore show the records a goal needs together, such as a currency with its countries and calendars. It shows them with the parts this page defines.

2.2. Words

One word per idea, in code, in the interface and in conversation.

Word Meaning
Record One row a person reads and maintains, such as one currency
Record kind What the records are, such as currencies. Named by its plural
List The paged table of the records of one kind
Record page The page of one record, with its tabs
Key The fields that name a record, such as iso_code. Fixed once the record exists
Display field The field a person knows the record by, such as the name
Version The number of a record's accepted change, used to refuse a stale write
History Every version of one record and the difference between them
Change reason The code and commentary that say why a write was made
Provenance Who wrote a version, for whom, when and why
Modified by, performed by The account that wrote a version, and the account it acted for. They differ when a service acts for a person, and read "<modified by> for <performed by>"
Link A row that joins two records and has no versions, such as a currency's country
Part A record that belongs to one parent and has its own versions, such as a calendar's rule
Field table The one list of a record kind's fields that drives its form, details, revert and write

2.3. What a record kind declares

A record kind is declared once. Every part of the screen reads the declaration, so no part repeats it.

Declaration Where What it decides
Resource: key fields, list reply field, subjects, parent field, versioned, writable REFDATA_RECORDS in wire-protocol/src/records.ts, or SYNTHETIC_RECORDS in bff/src/synthetic.ts What the BFF lets a screen reach
Store refdataStore or syntheticStore in web/src/refdata/store.tsx Which routes the screen's shared parts read and write
Write schema WRITES in bff/src/records.ts What a write must look like, checked against the generated write type
Field table A FieldSpec[] beside the screen The form, the details, the revert and the write
Columns A column list beside the screen The list's table
Permissions refdata::<plural>:write and :delete Which actions the screen draws

Every versioned record carries version, modified_by, performed_by, change_reason_code, change_commentary and recorded_at, and the screen reads them; it never asks a person for them.

2.4. The layers

Screen      list, record page, dialogs        web/src/refdata/*.tsx
Query       reads, writes, cache              React Query, keyed ['records', kind, parent?]
Route       HTTP, checked at the boundary     bff/src/records.ts  (/api/refdata/...)
Protocol    subjects, request and reply       wire-protocol/src/records.ts

A screen never names a subject or a wire field it was not given by its field table. The shared parts read and write through a record store: the refdata store by default, or the store a screen gives them with RecordStoreProvider. A store names its permission namespace, so its wildcard, such as synthetic::*, grants every kind it holds. A picker field names its own store, because a synthetic feed picks its currencies from reference data. The BFF answers only the record kinds its registry names. Adding a record kind adds a registry entry, a write schema, a field table and a screen. It never edits another kind's code; if it has to, a shared part is missing, and adding that part is the first task.

2.5. Addresses

Address Shows
/<area>/<plural> The list
/<area>/<plural>/:key The record page, on its first tab
/<area>/<plural>/:key?tab=<tab> The record page on one tab, so a link reopens it there

RS-ADDR-01 Add and Edit open a dialog over the page they start from; they have no address of their own. A record kind with a key of more than one field encodes the key in one path segment, as the pair code EUR/USD is.

2.6. The list

Area › Currencies
Currencies                                            [↻ Refresh] [+ Add]
The currencies the tenant works in.
[ 🔍 Search…                    ]  [ Tier ▾ ]
─────────────────────────────────────────────────────────────────────────
 Code   Name                 Tier       Nature     Symbol
─────────────────────────────────────────────────────────────────────────
 EUR    Euro                 (G10)      Fiat       €
 …
─────────────────────────────────────────────────────────────────────────
1–15 of 168         [« First] [‹ Previous] [Next ›] [Last »]   Page size [15 ▾]

2.6.1. Header

  • RS-LIST-01 The breadcrumb (Crumbs), whose shape is the Menu structure page's, then the plural as the title, in sentence case (PageHeader).
  • RS-LIST-02 One sentence that says what the records are.
  • RS-LIST-03 At the right, Refresh, then Import and Export for a kind that has an ORE document (Export stands on the record header instead when the document is one record; see RS-ORE-02), then Workflows, then Add. Add is the primary button and is drawn only for a person who may write. Workflows is a menu of the guided workflows that act on this kind of record, such as onboarding a party. It lists only those the person may start, and is not drawn when there are none. A workflow is not Add: Add is one record and one write in a dialog, and a workflow has several steps and its own address.

2.6.2. Search and filter

  • RS-LIST-04 A search box over the display field and the key. The search goes to the server through the list contract's filter, so it searches every record, not the page on the screen. A filter over the rows on one page is not a search.
  • RS-LIST-05 Where a kind has a natural classification, such as a tier or a type, a filter select beside the search. Where a kind hides some records by default, such as test tenants, a toggle that shows them. A filter is a ListFilter of the list's source; its value is held in the address as f.<id> and sent with the page.
  • RS-LIST-06 What the list says about the page sits above the table as a note: what the server's answer adds, such as how many records a filter hides, and what the screen knows before any read, such as why the list is read only.
  • RS-LIST-07 Where the generated list contract has no filter for a field the search needs, the contract is extended. The screen never reads every row to filter them itself. See Lists are always paged.
  • RS-LIST-08 A filter that matches nothing says "No <plural> match the search." and offers to clear it.

2.6.3. The table

  • RS-LIST-09 The columns come from one column list per kind. The display field or the key is the first column.
  • RS-LIST-10 One row opens the record page on a click, and on Enter when the row has focus. The table is reachable from the keyboard.
  • RS-LIST-11 Selection is single. Bulk changes are a separate feature, not a table mode.
  • RS-LIST-12 Every column sorts on the server through the list contract's order, on the value, not on the text shown. The default order is the kind's natural order: the display order, then the name, for reference data; most recent first for records that accumulate.
  • RS-LIST-13 The audit columns (version, modified_by, performed_by, recorded_at, change_reason_code, change_commentary) are hidden by default and offered in a column menu. tenant_id and an id that is a UUID are never shown.
  • RS-LIST-14 Which columns show, and in what order, is remembered per person in the browser, under a key that carries the column set's version.
  • RS-LIST-15 A cell follows the cell rules in Icons and cells.

2.6.4. Paging

Every list pages on the server. This is the rule the Qt standard had, and the memory Lists are always paged repeats.

  • RS-LIST-16 A list reads one page with offset, limit and the server's total, through the generated list contract. It never reads with a large fixed limit, and it never pages to the end to show everything.
  • RS-LIST-17 The footer is the shared Pager: the count first ("1–15 of 168"), then First, Previous, Next, Last, then Page size with 15, 25, 50, 100, 200 and 500. Every paged list starts at 15 rows, about one screen (DEFAULT_PAGE_SIZE); no screen sets its own default.
  • RS-LIST-18 Load all is offered only when the server's total is 1000 or less, and then reads that total in one page. It is the whole list by the server's count, never a cap, so it keeps the rule in Lists are always paged.
  • RS-LIST-19 A new search, a new filter, a new order or a new page size returns to the first page. A write reloads nothing and turns Refresh gold. Refresh keeps the page the person is on, after a write as after any change, and the marked rows and the strip say where the change fell; a record that the sort moved off the page is the strip's case. The person loses their place only when the page no longer exists, and then the list shows the last page (decided 2026-10-25 by the owner; the Entity screen specification's Refresh section says how the list shows a change).
  • RS-LIST-20 The page, page size, search and order are held in the address, so a link and the browser's back button restore the list.

2.6.5. States

RS-LIST-21 The list shows one of these states and never a blank table:

State What the list shows
First load "Loading…" in the table body. Never an empty table without words
Loading again The current rows, dimmed, and the count marked as refreshing. Never a blank table
No records "There are no <plural> yet." and, for a person who may write, Add
Nothing matches "No <plural> match the search." and a way to clear it
Error The server's words in an error notice, and Retry in place. Never a modal box
Changed on the server Refresh draws the eye (see Recency)

2.6.6. Recency

RS-LIST-22 When a change notification says a record of this kind changed, the list marks Refresh as stale. After a reload, a row that was not on the page before, or whose content is not what it was, gets a gold left edge and pulses three times, and the mark goes after four seconds (MARK_MS in refdata/changedRows.ts, three cycles of 900 ms in styles.css). The comparison is with the same page read before and uses no clock, so the first load marks nothing and moving to another page marks nothing. There is no control to turn the mark off (decided 2026-10-25; the rule is the Entity screen specification's Rows and entries that changed). The notifications come from Change notification.

2.7. The record page

Area › Currencies › EUR
Euro                                              [✎ Edit] [🗑 Delete]
EUR · version 4
[ Details ] [ Countries ] [ Calendars ] [ Desk groups ] [ History ]
┌────────────────────────────────────────────────────────────────────┐
│ ISO code   Name    Numeric code   Symbol                           │
│ EUR        Euro    978            €                                │
│ …                                                                  │
├────────────────────────────────────────────────────────────────────┤
│ Last changed: version 4 · priya · 2 hours ago ·                    │
│ Rectification · "Corrected the symbol"                             │
└────────────────────────────────────────────────────────────────────┘

2.7.1. Header

  • RS-PAGE-01 The breadcrumb, then the display field as the title.
  • RS-PAGE-02 The lead line reads "<key> · version <n>" (refdata.records.lead).
  • RS-PAGE-03 The actions at the right, in this order: the kind's own actions (such as Derive a calendar), then Edit, then Delete. Each is drawn only for a person who may take it, and only when the record allows it.
  • RS-PAGE-04 Edit stands on the thing it changes, and the version stands beside the thing it versions. A record of one kind is one card, so both stand in its header. An entity is a card per record, so both stand on each card, as the Entity screen specification draws them (decided 2026-10-25).

2.7.2. Tabs

RS-PAGE-09 The tabs are held in the address (useRecordTabs). In this order:

Tab Shown when Holds
Details Always, first The record's own fields, laid out by RecordDetails, then the last-changed line
<Field group> The kind declares a field group The fields of that group
<Related plural> The kind has links or parts of that kind One related-records panel
History The kind is versioned The timeline

A tab that does not apply to this record is not drawn: a derived calendar has no Rules tab. A tab that applies but holds nothing yet is drawn, with "None yet." and its Add.

2.7.3. Fields

  • RS-PAGE-05 A label above each value, in sentence case, with no colon.
  • RS-PAGE-06 The values stand in a grid of one column, two from the small breakpoint and four from the large one (RecordDetails, grid gap-4 sm:grid-cols-2 lg:grid-cols-4), in the field table's order, so a value stands where its field stands in the form. Under a value stands what it means or why it is fixed, in one line of small faint text, when there is something to say (decided 2026-10-25).
  • RS-PAGE-07 A value with nothing in it shows a muted dash, so a blank is not taken for a failure to load.
  • RS-PAGE-08 A classification shows its label (ClassifiedValue). A code that names another record is a link to that record's page.

2.7.4. Last changed

RS-PAGE-10 The Details tab ends with the provenance of the current version, in this order: version, modified by, performed by, recorded at, change reason, commentary. When modified_by and performed_by are the same person, the line names them once; otherwise it reads "<modified by> for <performed by>". The time follows the timestamp rule in Icons and cells: relative, with the exact value on hover. A record with no versions has no last-changed line.

2.8. Writes

RS-WRITE-31 Every write goes through one of three dialogs: the record dialog for create and amend (RecordDialog), the remove dialog for delete (RemoveRecordDialog), and the revert dialog (RevertDialog). A screen does not build its own.

2.8.1. The form

RS-WRITE-32 The form is drawn from the field table. One control per field kind:

Field kind Control Writes
text Text input, with a maximum length A string; a blank field may be empty
int Number input, with a minimum and maximum An integer
decimal Number input A number
bool Yes and No select A boolean
date Date input An ISO date
choice Select over fixed options, labelled from refdata.choices.<field> The option, or its number
classification Select over a classification list, showing name and code The code
record The searchable picker over another record kind (RecordPicker), showing name and code, and the flag when the field names a flag source The key
image The image field (ImageField): the image, large, opening the image chooser The image's identifier
cron The cron field (CronField), which says in words when the expression fires The expression
longtext A text area that spans the form's width. Not built yet: the control is the capture Add a long text field kind (decided 2026-10-25) A string
  • RS-WRITE-01 A field that names another record or a classification is always a select, never free text.
  • RS-WRITE-02 An optional field writes null when it is empty. A field with when is asked for only while the other values meet it, and writes null otherwise, as the rule form does for each rule kind.
  • RS-WRITE-03 The key is entered when the record is created and is fixed after that (fixed). So is a field that changes what the record is.
  • RS-WRITE-04 A field the server needs but the person does not edit, such as an image, is carried over from the record (keep). A field the screen sets, such as the parent of a part, is given by the screen (given).
  • RS-WRITE-05 A field that fails its check shows the message under it when the person leaves it, and focus moves to the first such field on save. Save is disabled until the form is complete.
  • RS-WRITE-06 Long text, such as commentary, is a text area, and a field table asks for one with the longtext kind. A field kind this table lacks, such as a time, a multiple choice, a percentage or an amount, is added to the table and to FieldInput before a screen uses it, as an icon is added to the icon table (decided 2026-10-25).
  • RS-WRITE-07 A field that follows from others, such as a key derived from two codes, is not asked for. The dialog fills it before the write (complete), and a revert fills it the same way.
  • RS-WRITE-08 A rule across fields, such as two codes that must differ, is said in the dialog before the save (check), and Save waits on it.

2.8.2. The form's layout

Decided 2026-10-25, from what the shared dialog does and what the Qt dialogs needed. Each rule names the part that carries it.

  • RS-WRITE-09 One dialog, no tabs. A form is one scrolling dialog (Dialog with wide, which is 48 rem). It never splits its fields over tabs, as the Qt currency dialog did. A kind with many fields declares groups in its field table, and a group is a titled section inside the one dialog, in the field table's order. A form with twelve fields or fewer declares no group.
  • RS-WRITE-10 Order. The fields stand in the field table's order: the key first, then the display field, then the rest in the order a person reads the record. The reason and the commentary come after every field, because they are about the change and not the record.
  • RS-WRITE-11 Two columns. The fields stand in a grid of two columns from the small breakpoint and one column below it (grid gap-3 sm:grid-cols-2). A longtext field spans both columns, and apart from it a field is never wider or narrower than its column.
  • RS-WRITE-12 Each field. The label stands above the control, in sentence case, with no colon (Field). Under the control stands one line of small faint text when there is something to say: the field's help, or what its value means (means), or why it is fixed. A wrong field shows its message under it when the person leaves it, in place of that line.
  • RS-WRITE-13 Required and optional. Nothing marks a required field. A required field left empty shows Required under it when the person leaves it, which is the message of the rule above on a field that fails its check, and Save waits. An optional select offers a dash as its empty choice; an optional picker offers Clear, as the Entity screen specification says. No label carries the word optional.
  • RS-WRITE-14 Buttons. The dialog's footer holds Cancel, then the primary button, Add or Save, at the right. Each carries its icon. Delete is never in the form: it is an action of the record page with its own dialog. While the closing guard is up, the footer holds Stay and Leave instead.
  • RS-WRITE-15 Keyboard. Escape asks the closing guard when there are changes and closes when there are none. Enter in a field does not save, because a save carries a reason the person must have chosen. Tab moves through the fields in the field table's order.
  • RS-WRITE-16 The dialog says when its record was loaded, under its title, and warns of a newer version above the form, as the Entity screen specification's An edit in progress says. This standard adds nothing to it.

2.8.3. Create

  • RS-WRITE-17 The dialog is titled "Add <thing>" and its button is Add.
  • RS-WRITE-18 A new record is written with the reason system.new_record, the only reason that applies to a new record, so the dialog states it in a note instead of asking. It is written with must_not_exist: adding a key that exists is refused, and the refusal names the key.
  • RS-WRITE-19 After the write the screen opens the new record's page.

2.8.4. Amend

  • RS-WRITE-20 Edit opens the record dialog with the current values. Its title is Edit followed by the title of the page, which is the display field, so the dialog and the page name the record the same way; a card's dialog is Edit followed by the card's title (decided 2026-10-25). Its button is Save.
  • RS-WRITE-21 The person picks a change reason from the server's reasons for an amendment (useReason('amend')), and writes a commentary when the reason needs one.
  • RS-WRITE-22 The reasons follow the change. When a field changed, common.non_material_update is not offered. When no field changed, only that reason is offered. The set is worked out from the difference between the form and the record.
  • RS-WRITE-23 The write claims the version read (must_match_version). A record that changed since is refused with 409, and the screen says so and offers to reload.
  • RS-WRITE-24 Leaving the dialog with changes asks "You have unsaved changes. Close anyway?"

2.8.5. Delete

  • RS-WRITE-25 Delete opens the remove dialog. Its title is "Delete <key>?" and it says what is lost, and whether history stays: a versioned record keeps its history; a link does not have one.
  • RS-WRITE-26 The person picks a reason for a deletion (useReason('delete')) and writes a commentary when the reason needs one.
  • RS-WRITE-27 The write claims the version read. A record that changed since is refused with 409.
  • RS-WRITE-28 Parts that belong only to this record are deleted first, with the same reason, and the dialog says so. Links from other records are not deleted, and the dialog says so.
  • RS-WRITE-29 The parts and the record are deleted one after another, because the server has no operation that deletes them together. If one fails, the dialog stays open and says which failed; the record itself is not deleted, so a retry finishes the work. A server operation that deletes a record with its parts in one transaction replaces this.
  • RS-WRITE-30 After the delete the screen returns to the list.

2.8.6. Read-only records

RS-WRITE-33 A record the server does not let a person change, such as a QuantLib calendar or a country, shows no Edit and no Delete, and says why in one notice on the Details tab. Its kind may still offer an action that makes a writable record from it, as Derive a calendar does.

2.9. History

The History tab is the shared timeline (Timeline), defined once in the Entity screen specification, section History and the timeline. A record screen draws it as that section says and adds two things:

  • RS-HIST-01 The stream is the record's story: its own versions and the versions of every part it holds now, merged newest first and each entry named by its part, read through the generic history request (GET /api/history, refdata.v1.history.get) once per record and per part. A part deleted from the record is not read, and the stream states that gap in words, on every record that has parts, as a standing caveat. A record's parts are few by design, dozens and not thousands, so the screen reads each part's history once; a kind whose parts are many needs a read by parent before it takes this rule (decided 2026-10-25, when the owner's run of the calendars scenario looked for an exception's change in the calendar's history).
  • RS-HIST-02 Revert is offered on any version but the current one, to a person who may write. It opens the revert dialog, which says that the values of version n are written back as a new version and that no version is lost. It asks for a reason, and writes against the current version.

RS-HIST-03 A screen that shows a log of events is an Audit screen, not History. It has Refresh and no comparison or revert.

2.10. Related records

A record page shows the records that belong to it as tabs, one per kind. There are two kinds of related records, and each has one panel.

2.10.1. Links

A link joins two records and has no versions, such as a currency's countries or a desk group's members. The links panel (LinkPanel):

  • RS-RELATED-01 lists the linked records by key and name; a name opens the linked record's page;
  • RS-RELATED-02 adds a link by picking a record that is not linked yet, and writes it with system.new_record;
  • RS-RELATED-03 removes a link with Remove, not Delete, because the linked record stays.

A link has no history, so the panel has none.

2.10.2. Parts

A part belongs to one parent and has its own versions, such as a calendar's rules, exceptions and diary events. The parts panel:

  • RS-RELATED-04 lists the parts in their natural order, with the columns the kind declares;
  • RS-RELATED-05 offers History, Edit and Delete on each row, and Add under the table;
  • RS-RELATED-06 opens the record dialog for add and edit, with the parent given, the shared timeline (Timeline) in a wide dialog for the one part, and the remove dialog for delete. The two-pane compare of HistoryPanel is the legacy shape and is not what this rule names (decided 2026-10-25).

Neither panel edits in the table. Neither offers Add while the parent is being created: the parent is saved first, and its page then offers the panel.

2.11. Import and Export

A kind that ORE reads as one document exchanges that document with ORE through two standard actions, Import and Export, and through nothing else: no screen parses or writes XML in the browser, and no kind draws a dialog of its own for it (decided 2026-10-25, when the currencies topic needed a server export and the owner asked for one standard pair across the screens).

  • RS-ORE-01 The pair is drawn for a kind that declares an ORE document: a row in ores_reporting_file_types_tbl whose configuration_type_code matches it, an input_document that ORE reads on its own, and the operations that serve it. The currency configuration, the calendar adjustments, the conventions and a pricing engine configuration are such kinds. A kind that has no row, or whose settings are a section of another document rather than a document ORE reads on its own, has neither button; the engine run settings are the second case, because they are the Setup section of the run's ore.xml.
  • RS-ORE-02 Import stands on the list header, between Refresh and Workflows (RS-LIST-03), because it brings records in. Export stands where the document's scope is: beside Import on the list header for a kind whose document is the whole table, such as currencies; on the record header, as the kind's own action (RS-PAGE-03), for a kind whose document is one record, such as an engine run setting. Each is drawn only for a person who may take it: Export needs read on the kind, and Import needs write on it, the preview included, because the preview is the first step of a write and a reader has nothing to review (RS-FEEDBACK-01).
  • RS-ORE-03 The words are the bare verbs Import and Export (RS-NAME-02), each with its icon from the icon table. The dialog names the document and its scope, such as "Writes currencyconfig.xml with 158 currencies."
  • RS-ORE-04 Export is one reply. The dialog states what it will write; on the press the server writes the document with the one ORE exporter and answers with the files, a name and a content each; the browser saves each under the name the server gave and the dialog states it. Nothing is written in the browser.
  • RS-ORE-05 Import is the five steps of the Configuration screen standard's Import section, on the steps UI at its own address under the list, as a workflow has (RS-LIST-03): the file, what it holds, what the system does not know, the target, and the difference with Keep it as the default for a removed row. The parse is the server's; the screen shows the counts and the lists and writes nothing before the review. The target step is drawn only for a kind that has configurations to choose between; for a table kind the target is the table and the steps are four. Every import carries a change reason and a commentary (RS-FEEDBACK-06), and nothing is dropped in silence.
  • RS-ORE-06 The server operations live in the ORE component's namespace, ore.v1.>, as the messaging reference requires of every subject, and take the verb-first shape of its operations: ore.v1.ops.export_<document> returns the files; ore.v1.ops.preview_<document>_import parses the file and returns what the import would do; ore.v1.ops.import_<document> writes with the person's choices and returns what it saved and the item errors. The kind's own component holds no XML; the ORE service reads and writes the kind through that component's messages, as its bundle import does today. The BFF serves the triple as GET /api/<kind>/ore-document, POST /api/<kind>/ore-import/preview and POST /api/<kind>/ore-import.
  • RS-ORE-07 One shared module carries the two buttons, the Export dialog and the Import steps (OreExchange). A kind declares its document once and draws nothing else. The currencies topic builds the first triple and the module; every later kind declares its document and gains the pair.

2.12. Flags and images

A screen never works out a flag itself. Flags come from one place, as they did in the desktop client's image cache.

  • RS-FLAG-01 The flag sources. A currency and a country have their own image. A calendar has its own, or else its country's. A business centre has its country's. A currency pair draws both of its currencies' flags. A code with no image draws the placeholder flag, the image with the code xx, so a missing image shows as missing.
  • RS-FLAG-02 One map. GET /api/image-map answers which image every flagged code uses (readImageMap). The browser reads it once (useImageMap), keeps it for five minutes, and reads it again after any record write, so a table of a hundred flags costs one read.
  • RS-FLAG-03 Images fetched once. Each image's bytes are read from its own address, /api/images/:id. The BFF keeps them in its tenant image cache, and the browser keeps them for a year, because an image's identifier is its identity.
  • RS-FLAG-04 Declared, not drawn. A screen names the flag source and the shared parts draw the flag: flag on a field (FieldSpec), on a list column (ListColumn), on a links panel (LinkPanel) and on a record header (RecordHeader). The parts use FlagOf, FlaggedCode and Flag from web/src/images/flags.tsx; a screen that draws a flag outside them, such as a party list whose rows carry their own flag, still uses FlaggedCode.
  • RS-FLAG-05 Choosing an image. A record that names an image, such as a currency's flag, edits it with the image field, which opens the image chooser (ImageChooser): a server search by code and description over a page of the tenant's images, with Clear. Adding images belongs to the storage screens.
Size Where
Small (20 by 14 pixels) Table cells, pickers, link panels
Large (36 by 24 pixels) A record page's header
Extra large (48 by 36 pixels) The image field and the image chooser

2.13. Icons and cells

RS-ICON-01 Icons come from the one Fluent set in Icon reference, at 20 pixels in buttons and 16 pixels in tables, monochrome and coloured by the theme. A standard action carries its icon beside its word; an icon never stands alone where its meaning is not plain.

Action Icon Word
Refresh arrow_clockwise Refresh
Add add Add
Edit edit Edit
Delete delete Delete
Remove a link link_dismiss Remove
History history History
Save save Save
Cancel dismiss Cancel
Revert arrow_rotate_counterclockwise Revert to this version
Search search (the search box)
Filter filter (the filter select)
Copy copy Copy
Import arrow_import Import
Export arrow_export Export

RS-ICON-02 arrow_clockwise and link_dismiss joined the asset set from the Fluent set for this table; Refresh does not borrow arrow_rotate_counterclockwise, which is Revert. An action this table lacks is added to it and to the icon reference before it is used, so two screens never draw one idea with two icons, and no icon carries two ideas.

RS-ICON-03 A cell is drawn as this table says:

Cell How it is drawn
A key or code Monospace, left
A number or amount Monospace, right, tabular figures
A classification Its label (RowLabel); with no label, its name
A code with no label mapping The orange dashed fallback label, so the gap shows
Yes or no The words Yes and No
A timestamp Relative, with the exact value on hover
A country, currency, calendar or business centre Its flag, then its code (FlaggedCode)
A currency pair Both currencies' flags, then the pair code
An empty value A muted dash

2.14. Feedback, access and safety

2.14.1. Feedback

What happened What the screen says
A write succeeded The dialog closes. The page does not reload: the write's own change event turns Refresh gold, and the person presses it to see the new values (decided 2026-10-25; see the Entity screen specification, Refresh)
A field is wrong The message under the field, and the list of fields still to fill in
The server refused The server's own words, in the dialog, which stays open
The record changed since it was read That it changed, and an offer to reload
The key already exists That it exists, naming the key
The session ended That it ended, and a way to sign in again
The server cannot be reached That it cannot be reached, and Retry

RS-FEEDBACK-07 Every message says what happened and what to do. "Error" on its own is not a message, and the server's own words are shown where a write was refused.

2.14.2. Access

  • RS-FEEDBACK-01 An action a person may not take is not drawn, rather than drawn disabled. useRecordPermissions decides from the registry and the person's roles.
  • RS-FEEDBACK-02 Whether the person may change a record is shown by the access mark, Editable or Read only, in the record header and beside a form panel's title, as UX Language sets it. A screen never shows a permission code.
  • RS-FEEDBACK-03 The server decides; the screen only hides what the server would refuse. A refusal is shown as a refusal, not as a failure.

2.14.3. Safety

  • RS-FEEDBACK-04 Delete always confirms, names the record, and says what is lost.
  • RS-FEEDBACK-05 Revert always writes a new version and says so.
  • RS-FEEDBACK-06 Every write carries a change reason; there is no silent write.

2.15. Naming

Thing Rule Example
Record kind key Plural, kebab case currency-pairs
Address Area, then the kind key /refdata/currency-pairs/EUR%2FUSD
BFF route /api/refdata/<kind key> /api/refdata/currency-pairs
Query key ['records', kind, parent?] ['records', 'calendar-rules', 'ACME_LDN']
Screen components <Plural>Page and <Singular>Page CalendarsPage, CalendarPage
Path helper <singular>Path(key?) calendarPath('TARGET')
Field labels refdata.fields.<field> refdata.fields.calendar_type
Choice labels refdata.choices.<field>.<option> refdata.choices.shift.none
Shared words refdata.records.* refdata.records.save
Interface words Sentence case "Calendar type"

RS-NAME-01 Interface words are in sentence case, as the table says.

RS-NAME-02 The verbs are Add, Edit, Save, Delete, Remove (for a link only), Revert, Refresh, Cancel, Propose, and Import and Export (for an ORE document only). This is the one list; the Entity screen specification points here. A kind's own action is a verb and its object, such as Derive a calendar or Rebuild business days.

2.16. Exceptions

RS-EXCEPT-01 Some record kinds are not regular. The exception is named on the kind's screen and in its journey, never found later, and the list of how today's screens conform names each part a screen lacks and why.

Kind What differs
Classification list Short code lists with an order and a label; maintained from the classifications screen, whose rows follow this standard. A list is read whole once, then searched, ordered and paged in the browser, because it is short and reordered as a whole. Reorder is the list's own action: a mode with its own save
Link No list or page of its own; maintained from a parent's links panel
Part No list or page of its own; maintained from a parent's parts panel
Read-only No Edit and no Delete; the reason shown on the Details tab
Shaped by its source Tabs depend on what the record is made of, such as a QuantLib, derived or bespoke calendar
Singleton One record by definition: no list, no Add, no Delete
Accumulating Most recent first; Delete usually refused
Mixture A set of parts that are read and judged as one, such as an FX feed's Gaussians. The tab lists the set across the full width, with the previews it implies side by side below it and the engine's note under them. A row opens the part in a dialog; each row moves up or down and is deleted from the set; Add opens the dialog empty. What is not saved is marked: a new row, a changed cell, a moved row, and a deleted row kept struck through with Undo, with a count beside Save. Save is one button beside Add, enabled while there are changes; it opens a dialog that asks the reason and says when the parent uses the change. The part's history is not offered row by row: the parent's History tab is one stream of the parent and every part, the timeline of the entity screen specification, with Revert on each older version

RS-EXCEPT-02 An exception decides which parts exist and which actions show. It never changes the layout of a part, and it never brings a new icon or a new word for an existing idea.

2.17. The shared components

Each rule above is carried by a shared component. The status says how far the component carries it today.

Rule Component Status
Record kinds reachable from the browser REFDATA_RECORDS, /api/refdata routes Exists
Write checked at the boundary WRITES Exists
Field table, form, details FieldSpec, FieldInput, RecordDialog, RecordDetails Exists, with a message under a field the person leaves wrong, derived fields (complete) and rules across fields (check); no text area yet
Record store RecordStore, RecordStoreProvider, useRecordStore in web/src/refdata/store.tsx Exists for refdata and synthetic
Tabs in the address useRecordTabs Exists
Record page reads one record RecordGate, useRecord, GET /api/refdata/:kind/key/:key Exists for kinds with a one-field key
Record page header RecordHeader Exists
Paged list Pager in ui/Pager.tsx Exists: First, Previous, Next, Last, page size and Load all
List screen RecordList, RecordTable, useListState Exists: server paging, search, filters and order held in the address, notes, column menu, keyboard opening, the states. RecordTable is the list without its header, for a tab that lists records the page only reads
Server paging, search and order listRecordPage, the model's :searchable: and :sortable: columns Exists for currencies, desk groups, currency pairs and calendars
Record pickers RecordPicker Searchable and flagged; Partial: its choices are its whole kind, read once and searched in the picker
Flags useImageMap, useFlags, FlagOf, FlaggedCode, Flag in images/flags.tsx; GET /api/image-map Exists
Image choice ImageChooser, ImageField in images/ImageChooser.tsx; GET /api/images Exists, searched on the server
Change reasons useReason, reasonsFor, ReasonFields Exists, filtered by whether a field changed
Unsaved changes useCloseGuard, LeavingFooter Exists
Delete DeleteDialog, RemoveRecordDialog Exists; DeleteDialog takes the write, RemoveRecordDialog deletes a refdata record with it; parts are deleted one after another
Revert RevertVersionDialog, RevertDialog Exists; RevertVersionDialog takes the write, RevertDialog reverts a refdata record with it
History Timeline in timeline/Timeline.tsx Exists
Links LinkPanel Exists
Parts PartsPanel in refdata/records.tsx Exists, beside LinkPanel
Labels and fallback RowLabel, ClassifiedValue, ui/Label.tsx Exists
Icons Icon in ui/Icon.tsx, the icon of Button Exists for the actions in the icon table
Last-changed line LastChanged, drawn by RecordDetails Exists
Timestamps RelativeTime in ui/Time.tsx Exists
Recency useChangedRows in refdata/changedRows.ts Exists
Permissions useRecordPermissions Exists
Access mark AccessMark in ui/AccessMark.tsx, drawn by RecordHeader Exists
Workflows of a kind WorkflowMenu in shell/WorkflowMenu.tsx Exists on Parties
Import and Export OreExchange in ore/OreExchange.tsx; ore.v1.ops.export_<document>, preview_<document>_import, import_<document> Exists for the currency configuration: the Export dialog on the currencies list and the Import steps at /refdata/currencies/import; the other documents declare theirs

2.18. How today's record screens conform

Screen Where it differs from this standard
Currencies, desk groups, currency pairs Conform, except recency and the whole-kind pickers; a desk group reads its members from the whole junction, which has no read by group
Holiday calendars Conform, except recency and the whole-kind pickers; the business days follow the server's rebuild, which never rewrites a built day and does not read QuantLib holidays (both captured)
Classifications Conform, under the classification list exception: a list is read whole once and paged in the browser
People Conforms: the shared list over the accounts, and a page per person with details, contact, roles and sign-ins as tabs; roles are read one person at a time, as the server has no joined read
Tenants Conforms: the roster searches and sorts, the page has a History tab, and the tenant's parties and people are shared record tables in its tabs that search and sort
Parties Conforms as an entity: the shared list, and a page per party with its details, identifiers, contacts, memberships, structure and history, each changed in a dialog. A party has no Delete, because it is retired by its status. Its History tab is the timeline. A new party is provisioned by a workflow, listed in the Workflows menu. A change writes directly until the four-eyes approval of party changes is built. The tabs are drawn for what the reader may read, an opened tab stays drawn, and Refresh reads only what the page shows. It lacks a type and a status filter on the list, because the record list contract has no filter yet
Reports Conforms to the list and the record page over the report definitions of the parties the session sees: the server's page and total, the party tag by name, the party filter, the shared states and pager, and the record dialog, all from the shared parts. It differs where the server does not answer: there is no search over definitions and no sortable column, because the generated read declares neither; there is no tally by health and no filter on it, because nothing counts the rows by health; there is no History tab, because the versions are looked up by the name the model declares as the key, and a name is shared by the same report in every party, so the tab would answer with another party's versions (the story's task "Key the report definition event by id" carries the model change); and the page offers no Delete and no Add, because no BFF route removes or creates one. An amend of a definition of a party other than the session's acting party is refused by the store with a duplicate-key fault, because the write's row-level scope cannot see the row the trigger must close (the story's task "Amend a definition that belongs to another party" carries that fix). The badge seed has no definition for the health word completed with errors, so that word draws the catalogue's unmapped badge until the seed gains one
Roles Their own list and page layout; the list reads every role, and the role page reads every account with its roles
Synthetic feeds Conforms to the Feed screen standard, which extends this one for feeds
Subscriptions Conforms to the list's header, refresh, states and words for the subscriptions and the routes of the party it acts in, and builds both tables from the shared column and cell rules. It differs where the server does not answer: the lists are two record kinds read whole rather than paged, so there is no search, no server order and no pager; there is no row that opens a record page, so Add and Delete are dialogs over the list and there is no Details tab and no History tab, because no BFF route reads the versions of a subscription or a route; and the deletion names the provider the service keys the row by, because the party is the session's
Market data sources A board and not a record screen: it reads one adapter status per source and draws the tiles, the alert, the table and the detail panel of its prototype. It has no Add, no Edit and no Delete, because nothing on the web writes a source, and the fields the status does not carry are stated as gaps
Cut types Conform: the shared list over the kinds, server paged, searched and ordered on the server; the record page with the details, the cuts of the kind and the history; the record dialog for the amend and the remove dialog for the delete. It lacks Add, because a kind's retention destination is a tier no lookup states, so a new one would have to invent the value; and the retention destination is stated on the page and carried over by the write rather than offered as a field, for the same reason
Cuts Conform as a record the web only reads: the shared list over the bodies, server paged and narrowed by kind and state, and the record page with the body, the acts, the runs that priced from it and the history. It lacks Add, Edit and Delete, because a cut is taken, never typed, and the take flow is a journey of its own; the state and the label are derived, so the record states no versions of its own beyond the row's; and its Runs tab names the runs and leaves the comparison of two runs to the audit journey

The story Bring the record screens to the standard closes these differences.

2.19. Building a record screen

RS-BUILD-01 Work in this order. Each step ends in something to check, and the last step tries the screen on the running server.

  1. Name the record kind, its key, its display field and its natural order. Say whether it is versioned and writable, and whether it is an exception.
  2. Add the registry entry and the write schema, with a test that the BFF refuses a bad row and a read-only write.
  3. Write the field table and the column list.
  4. Build the list from the shared parts: header, search, RecordTable, Pager.
  5. Build the record page from useRecordTabs, RecordDetails, the related panels and the Timeline.
  6. Use RecordDialog, RemoveRecordDialog and RevertDialog for every write.
  7. Add the strings in English, French and Portuguese.
  8. Check it on the running server: list, page, search, open, add, edit with a stale version, delete, history and revert, and the empty, error and no-permission states.

2.20. Where this departs from the Qt standard

The Qt standard is the source of this scope. These are the places this page decides differently, on purpose.

Qt Here Why
Edit, Delete and History on the list toolbar, acting on a selected row The row opens the record page, where those actions are A web page has an address; the record page is where a person acts on one record
A detail dialog per record A record page, with dialogs for add and edit A page can be linked to and reopened on a tab
A Provenance tab A last-changed line at the end of Details Six values do not need a tab
A reason dialog on create offering the system reasons The one creation reason stated in a note Only system.new_record applies; a choice of one is not a choice
Modal boxes for errors A notice in place, with Retry It keeps the person's place and can be retried
No search on the list Server search and one filter No one pages through four hundred records to find one
Labels with a trailing colon Sentence case, no colon The web convention; it reads the same
Disabled actions Actions not drawn A disabled button asks a question a missing one does not

3. See also

Emacs 29.3 (Org mode 9.6.15)