enhancement
major
minor
major
minor
Goal
The View Designer can display a view and edit scalar properties, but it cannot be used to actually build one. This ticket completes it to the point where an element can be created, filled in, placed and saved without leaving the designer or restarting the application. Everything found along the way is covered here.
Defects
Structural editing did not reach the configuration
Add Child, Remove and Move mutated only the DesignTreeNode mirror tree, never the underlying ConfigurationItem, so Apply serialized unchanged configurations and no change was persisted. Three separate causes were involved:
- The commands never touched the owning configuration property.
- ReactTreeControl pushed context-menu items as state that no client component renders, so the menu never appeared at all.
- Only AppShellElement provided a ContextMenuOpener, so a view without an app shell (the designer itself) had no overlay to render a menu into. The window-level overlays (menu, dialog manager, snackbar) now belong to the window.
Adding gave no choice of element type
AddChildCommand hard-coded StackElement, and the form's list editor silently took the first polymorphic option. A container of UI elements accepts on the order of 60 types, so they are offered as a menu of the types the target property actually allows.
A saved view could break the application
An editor can produce a configuration that serializes but that the view loader rejects - an element added but not filled in, or two entries of a keyed list sharing a key. Writing it left the application unable to load the view. Checking individual constraints is the wrong approach, because the reader enforces many; the serialized view is now read back through the loader's own parser before anything is written.
A view that cannot be loaded took down the whole application
A single defective .view.xml made the entire application unusable, because ReferenceElement.createControl threw when the referenced view could not be loaded. The exception propagated out of the whole control tree, so the page failed with an internal error instead of rendering - navigation, app bar and the View Designer unreachable as well, including the designer needed to repair the defect.
A view that fails to load now replaces only itself: ReferenceElement renders a placeholder naming the view and the reason, so the rest of the application keeps working and the defect is visible where it occurs. The design tree already behaved this way through ErrorDesignTreeNode; the application side was missing.
A defective root view still cannot render: there is no enclosing control tree to fall back to, and the designer cannot build a design tree from an unparseable file either. Covering that needs a fallback page plus a designer that can present a file it cannot parse.
An added element could not be filled in or used
- A newly added node was constructed directly instead of through the tree builder, so it was not bound to its own container properties: nothing could be added to a tab that had just been created, until the whole designer was re-opened.
- Editing an identifying property left the tree showing the previous label.
- An element added to a keyed list got no key, so a tab was created with an empty ID - and TypedConfiguration rejects a second entry with the same empty key, so a second tab could not be added at all.
- A structural edit collapsed the tree; the expansion is kept, and removing an element selects its parent.
- A tab with no label rendered as a blank tab, so a tab added in the designer was invisible in the application. The label falls back to the tab ID while no label is configured.
Editors for every kind of property
Several kinds of property had no editor at all, so a view could not be completed in the form:
- A property whose values are not strings (e.g. a channel reference input="model") was edited through a plain text input that handed the property a string it cannot store, so the entry was silently lost. Such a property is now edited as the specification the configuration file uses for it, converted by the property's own value format.
- Model attributes and configuration properties had separate sets of editors, so every editor had to be built twice. There is now one set, resolved by the Java type of the edited value (FieldControlRegistry in the React layer), fed both by the model side and by configuration properties. A single property deviates through a FieldControl annotation.
- A DERIVED property was skipped entirely instead of being displayed read-only. A property is now hidden only when it says so.
- An annotation declared on the property a configuration overrides was not seen, so @Hidden had no effect on an inherited property - the type property of a polymorphic configuration was offered for editing.
- A property holding a configuration item that the configuration writes as text - a TL-Script expression - was rendered as a form over its syntax tree, and not at all while unset, so a script could not be entered.
- A script in a configuration is now edited in the same TL-Script editor the script console uses, with syntax colouring and inline parse diagnostics. The control editing a kind of value can be declared for a plain Java type, so a value type that no model type describes reaches its editor from the module defining it.
Only generally usable implementations are offered
The element, command and action selectors offered every implementation, including many written for one single use case. They now offer the implementations classified as generally usable through @InApp, which reduced the element menu from 63 to 40 entries. The implementations were classified accordingly.
A generic action must not execute its input
The script action of the React layer compiled and executed whatever value arrived on its input channel, so any configurable data path was a code path. The generic action takes its script from its configuration and passes the command's input to it as argument. Compiling the input itself stays with the TL-Script console, in an action that claims no tag and is not offered as a building block.
A polymorphic configuration reference must use a wildcard
A property referencing a polymorphic configuration must name its base type as a wildcard bound, because the value stored in the property is the configuration of an implementation of that type:
{{{#!java PolymorphicConfiguration<? extends Renderer> getRenderer(); // correct PolymorphicConfiguration<Renderer> getRenderer(); // wrong }}}
Without the wildcard the property does not describe what it can hold, and the declaration is invisible to a search for the properties of a given base type. That is how these were found: the ViewAction selectors of the view layer could not be located by searching for PolymorphicConfiguration<? extends ViewAction>. Those five declarations are corrected here.
279 further declarations in 21 modules remain. The complete list - one line per declaration as module :: path:line declaration, grouped by module - is checked in at
docs/sweeps/polymorphic-configuration-wildcards.md
so that it can be diffed while the sweep progresses. It is regenerated with:
grep -rnE "(Named)?PolymorphicConfiguration<[A-Z]" --include=*.java . | grep -v /target/ \\
| grep -v "extends PolymorphicConfiguration<" | grep -E "(get|set|is)[A-Z]\\w*\\s*\\("
Not affected, and not to be changed: interface Config extends PolymorphicConfiguration<MyImpl> - a configuration correctly names the implementation it configures - and a parameterization by a type variable.
Adding the wildcard makes a stream of the configured instances yield a captured type, so collecting into a typed list needs an explicit type witness, e.g. .<ViewAction> map(context::getInstance).
Verification
A tab, a panel inside it and a text element inside that were created through the designer alone, the tab ID and the text's channel binding entered in the configuration form, and Apply pressed. The result is
{{{#!xml <tab id="designer-tab">
<panel>
<text input="gridAnchor"/>
</panel>
</tab> }}}
and the running application shows the new tab, selects it, and renders the bound channel value - without a restart.
The TL-Script editor was verified on a script property of an existing action and on a freshly added one: it shows the real source, an edit is kept, and a syntax error appears as an inline diagnostic. The script console is unaffected.
Migration
An application implementing the React field control provider of the view layer must use the shared interface instead:
com.top_logic.layout.view.form.ReactFieldControlProvider -> com.top_logic.layout.react.field.ReactFieldControlProvider
The method keeps its role but receives a FieldSpec describing the edited value instead of the model attribute, so the same control serves a model attribute and a configuration property. A provider registered for a model type through FieldControlService needs no change.
An application implementation of a UIElement, ReactCommand or ViewAction that should be offered in the designer's selectors must carry @InApp; without it, it remains configurable but is no longer listed.
Not addressed
Serialization normalizes the file and drops XML comments. The normalized layout is intended; comment loss is a remaining consequence.