Record screen standard
Table of Contents
- 1. Summary
- 2. Detail
- 2.1. Where this standard applies
- 2.2. Words
- 2.3. What a record kind declares
- 2.4. The layers
- 2.5. Addresses
- 2.6. The list
- 2.7. The record page
- 2.8. Writes
- 2.9. History
- 2.10. Related records
- 2.11. Import and Export
- 2.12. Flags and images
- 2.13. Icons and cells
- 2.14. Feedback, access and safety
- 2.15. Naming
- 2.16. Exceptions
- 2.17. The shared components
- 2.18. How today's record screens conform
- 2.19. Building a record screen
- 2.20. Where this departs from the Qt standard
- 3. See also
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
ListFilterof the list's source; its value is held in the address asf.<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_idand anidthat 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,limitand the server'stotal, 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
optionalfield writes null when it is empty. A field withwhenis 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
longtextkind. A field kind this table lacks, such as a time, a multiple choice, a percentage or an amount, is added to the table and toFieldInputbefore 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 (
Dialogwithwide, 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). Alongtextfield 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 withmust_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_updateis 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 ofHistoryPanelis 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_tblwhoseconfiguration_type_codematches it, aninput_documentthat 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 theSetupsection of the run'sore.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>_importparses 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 asGET /api/<kind>/ore-document,POST /api/<kind>/ore-import/previewandPOST /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-mapanswers 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:
flagon a field (FieldSpec), on a list column (ListColumn), on a links panel (LinkPanel) and on a record header (RecordHeader). The parts useFlagOf,FlaggedCodeandFlagfromweb/src/images/flags.tsx; a screen that draws a flag outside them, such as a party list whose rows carry their own flag, still usesFlaggedCode. - 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.
useRecordPermissionsdecides 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.
- 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.
- Add the registry entry and the write schema, with a test that the BFF refuses a bad row and a read-only write.
- Write the field table and the column list.
- Build the list from the shared parts: header, search,
RecordTable,Pager. - Build the record page from
useRecordTabs,RecordDetails, the related panels and theTimeline. - Use
RecordDialog,RemoveRecordDialogandRevertDialogfor every write. - Add the strings in English, French and Portuguese.
- 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
- UX — the structure note that orders this cluster, and where to read this page in it.
- The entity specification (superseded) — the Qt-derived standard this page replaces for record screens
- Qt account screen: raw reference analysis — the Qt dialog, save, delete and history flows in detail
- Lists are always paged
- UX Language — colours, labels, and History versus Audit
- UI design principles
- Screen review checklist — the rules of this page as a list to tick off
- ORE element and leaf screen standard — what a record page adds when its fields are the terms of an ORE document, such as a convention
- Menu structure — the entry each record screen belongs to, which is the middle of its breadcrumb
- Icon reference
- Badge catalogue
- Change notification
- ores.web component specification — the routes the record screens use
- NATS Entity Protocol Specification — the list contract: offset, limit, total and order
- User Journeys