enhancement
major
minor
major
minor
minor
#29561
TL Views: display of a referenced object does not follow a change of its label (table cell, form field)
Affects 8.0.0-SNAPSHOT (2026-09-11), tl-layout-view / tl-layout-react.
Wherever a TL Views UI displays a referenced object by its label - a reference column of a <table>, a reference field of a <form> in view mode, the chips of a select field in edit mode - the label stays as it was when the display was built. A change of the referenced object's name (an I18NString name as in the example below, but just as well a plain String name) is not reflected until the whole UI is rebuilt, e.g. by switching the session's language. A browser reload does not help.
Reproduction
Type expenses:ExpenseCategory with a mandatory name of type tl.model.i18n:I18NString (no TLLabel annotation; the label provider resolves the name part and localizes the ResKey). Type expenses:Expense has a reference category to it, shown as a column of a <table> in another view.
- In the categories view, edit the category travel: name Reise → Reisen (German), Travel → Trips (English), save. The categories table itself shows Reisen at once.
- Open the expenses view: the category cell of an expense referring to that category still reads Reise - also after a full browser reload of the page. The persisted value is already Reisen (checked in the script console).
- Switch the UI language to English: the cell reads Trips; switch back to German: Reisen. Only the language switch (which rebuilds the session's UI) refreshes the label.
Expected: editing an object's label attribute is reflected wherever the object is displayed, at the latest on the next render of the cell.
Analysis
There is no label cache between model and cell: I18NAttributeStorage builds a fresh literal ResKey on every read, the resource bundles cache nothing per key, and the type's label provider (IDColumnLabelProvider over the name part) reads the attribute value on every call.
What is stale is the React control tree:
- A reference attribute is displayed by a ReactDropdownSelectControl (via FieldControlService.createDisplayControl in table cells, via the form field in forms). The control computes the label of its selected object(s) once, when it builds the value descriptors sent to the client, and recomputes them only when its field value changes. Nothing observes the objects being displayed. The table refreshes its rows only for changes of its observed-types (the row type), not for changes of objects its cells refer to; that is why the categories table (observing ExpenseCategory) updates and the expenses table (observing Expense) does not.
- A browser reload does not rebuild the tree: ReactWindowRegistry keeps the window's control tree and model scope across an unload/reload and re-renders the existing tree. Only a language switch tears the session's windows down.
- ReactResourceCellControl resolves the label of its object once in the same way.
Solution
A control that displays model objects by their label observes those objects while it is attached, and refreshes its display when one of them changes.
- com.top_logic.model.listen.ObservedObjects (tl-core): a changing set of TLObjects a single ModelListener observes on a ModelScope. attach(scope) / detach() follow the display lifecycle (the set survives a detach and is registered again on the next attach), observe(objects) / observeValue(value) re-point the observation as a difference (registration by tId(), objects without an identity skipped). ChannelObjectObserver in the view layer uses it in place of its own registration code.
- ReactDropdownSelectControl observes the objects of its value while attached (attach/detach listeners of the control, scope from ReactContext.getModelScope() via ReactControl.modelScope()), follows a value change - also one originating from the client - and on a change of an observed object re-sends its value descriptors and drops the loaded option list, so the client reloads it on the next open. This covers table cells, view-mode form fields and the chips of an edit-mode select field.
- ReactResourceCellControl observes the object it displays in the same way and resolves label, icon, tooltip, css class and link again on a change.
- DefaultReactContext.getModelScope() answers null for a context without a window registry (a control built outside a browser window observes nothing) instead of failing; ReactControl.modelScope() is the accessor controls use.
- Demo (tl-demo-react): the contributor dialog of the project drill-down (projects/contributor-dialog.view.xml) edits the contributor with a transactional Save, so renaming a contributor on top of the tickets table shows the live refresh of the assignee cells and of the ticket detail's assignee field.
Tests: TestObservedObjects (tl-core), TestSelectValueObservation and TestResourceCellObservation (tl-layout-react, knowledge-base based: rename the displayed object in a transaction and assert the control's state carries the new label; a never-displayed or detached control is not refreshed; the observation follows a new value).
Not covered: plain text cells of rows without a model part, and the table's row-refresh policy (observed-types), which is unchanged.