enhancement
major
minor
major
minor
Follow-up to #29525 (the app-bar user menu of the com.top_logic.layout.view layer). Usability feedback on the first version, and what is done about each point.
Login is hidden behind the menu
Login hinter Menü versteckt, schwer zu finden. Wenn kein Nutzer angemeldet ist, sollte "Login" direkt sichtbar sein.
An anonymous session sees an avatar of the anonymous account, and Login only after opening it. The account area (user-menu.view.xml) becomes a <switch> over the current user: an anonymous session gets a plain Login button and nothing else - no avatar, no menu, so no theme switching before logging in - and a logged-in session gets the menu, which loses its Login entry. Logout stays in the menu: leaving is looked for by the person who is logged in, and the menu is where the rest of their account is.
The switch needs to tell the anonymous account from a user's, which TL-Script could not: accountIsAnonymous() (AccountFunctions in tl-model-search) answers it for any account, so currentUser().accountIsAnonymous() is the test.
Section labels
Sektions-/Gruppenlabels fehlen. Sprach- und Themeauswahl sollten "Sprache" und "Theme" oder "Erscheinungsbild" als Label für die Sektion bekommen.
A menu separates its groups by a line and nothing else, so the reader has to recognise the languages and the themes by their entries. A group of a menu may now carry a heading:
- ContextMenuContribution gains an optional label; ContextMenuOpener emits it as a MenuEntry of a new kind header at the top of the contribution, after the separator, and only when the contribution has visible entries.
- TLMenu renders the header as a non-focusable, non-selectable caption (not a menuitem; excluded from keyboard navigation).
- AbstractMenuElement.Config.CommandGroup gains a label (ResKey), and ViewCommandSource.Config gains one too. <language-commands> and <theme-commands> default theirs to "Language" / "Sprache" and "Theme" / "Theme", so every menu using the sources gets the headings without writing them; a view overrides the text where it disagrees.
Found on the way: a menu taller than the viewport scrolls, but moving the focus with the arrow keys did not scroll the focused entry into view, so the focus left the visible part. The focused entry is now kept in view.
The active choice is marked, not disabled
Die aktive Sprache und das aktive Theme werden im Menü deaktiviert dargestellt. Das ist kein gutes Feedback für "aktiv".
The language and theme sources marked the choice in force by offering its entry as non-executable, which the menu shows greyed out - the reader cannot tell "this is what you have" from "this is not available". A command now has an explicit active state, independent of executability and visibility:
- CommandModel.isActive() (default false) says whether the command's effect is currently in force - the alternative chosen among a set, or a toggle that is pressed; SimpleCommandModel.setActive(BooleanSupplier) configures it, read on every display like executability.
- A menu entry of an active command carries the CSS class tlMenu__item--active and aria-current="true" and is set in bold (MenuEntry gains an active component, sent as active by ReactMenuControl, passed by ContextMenuOpener). The entry stays selectable.
- A button of an active command carries tlReactButton--active and aria-pressed (ReactButtonControl mirrors the model state). This is the rule the toggle button already used under the name tlReactButtonActive; TLToggleButton shares the renamed class.
- <theme-commands> and <language-commands> mark the theme and the language in force as active; both entries stay executable. Choosing the active language again neither stores nor reloads.
Actions are labelled with verbs
Bei Aktionen Verb benutzen: Abmelden anstatt Abmeldung.
- Menu: Abmeldung → Abmelden, Anmeldung → Anmelden (also the Login button above).
- Login dialog: the submit button Anmeldung → Anmelden; the dialog title stays a noun.
Settings dialog: Save and Cancel
Einstellungen: Speichern und Abbrechen statt Schließen.
The dialog's own button is relabelled Cancel / Abbrechen, and Save closes the dialog: each tab's Save chain becomes <with-transaction><store-form-state/></with-transaction><close-dialog/> (base account tab in settings-tabs.view.xml and the profile tab overlay in tl-layout-view-contact). With Save closing the dialog, the two buttons read as the usual pair of a form dialog. On the security tab, which holds actions rather than fields, Cancel merely closes.
Settings > Security: no status of the second factor
Kein Status bei 2FA/Multi-FA Auth. Lösung: Label / Statuszeile.
The tab offers set up or switch off and lets the reader infer the state from which button is shown. A status line above the buttons states it: "Two-factor authentication: active" / "not active", a <switch> over the account evaluating whether a secret is stored - the same condition that decides which button is offered. No date is recorded, so the model is unchanged.
Settings > Profile: consistent labels
"Nachname" anstatt "Name"; Telefon und Telefon mobil ausschreiben.
- The profile form labels the name field "Last name" / "Nachname" through the field's own label (FieldElement.Config); the model label stays "Name", since Contacts:Contact#name is the company name for a company contact.
- The model label of Contacts:Contact.Person#phone_mobile changes from "Tel. mobil" / "Phone, cellular" to "Telefon mobil" / "Mobile phone" in model.Contacts.messages_*.properties of com.top_logic.contact; phone already reads "Telefon".
Confirmation dialog offers Maximize
Bestätigungsdialog (z.B. persönliche Einstellungen zurücksetzen): Dialog maximieren braucht es an dieser Stelle nicht.
ConfirmDialogControl builds its ReactWindowControl with the default resizable=true, and TLWindow shows the maximize button for every resizable window. A confirmation is a fixed-size dialog, so the control sets resizable=false, which drops the button and the resize handles.
Timezone must not be empty
The timezone of an account is required for every date the user sees, and tl.accounts:Person#timezone has a default value provider, so it is marked mandatory (model plus update-property migration Ticket_29540_mandatory_timezone, as in Ticket_29512_mandatory_model_attributes.migration.xml). A mandatory single select renders no clear affordance at all; the select control's own clear buttons are #29541's concern.
Verification
Verified in the browser on the React demo application (tl-demo-react), in German and English: the anonymous app bar shows the Login button and no avatar or menu; logged in, the menu lists Abmelden, the account group, then "Sprache" and "Theme" as captions above their entries, which the arrow keys skip; the current language and the current theme are the only bold entries of their groups, enabled, and choosing them again closes the menu without a reload while choosing another one switches and moves the mark; Save closes the settings dialog and the saved value persists; the security tab states the second-factor status; the profile labels read Nachname, Telefon, Telefon mobil; the confirmation dialog has no maximize button; the timezone field has no clear button after the migration ran on an existing database; no console or server errors. Unit tests cover the active state on ContextMenuOpener and ReactButtonControl.
Migration
Behaviour changed, no code change needed.
- tl.accounts:Person#timezone is mandatory. Existing databases are migrated by Ticket_29540_mandatory_timezone; the attribute has a default value, so no account is left without one. Code that sets the timezone of an account to null now fails validation.
- A menu heads a group contributed by <language-commands> or <theme-commands> with "Language" / "Theme". An application that wants other headings sets <label> on the source, or none by setting it to an empty key.
- The account area of user-menu.view.xml renders no avatar and no menu for an anonymous session, only the Login button. An overlay contributing entries meant for anonymous users must place them elsewhere.
- The entries of the theme and the language in force are enabled and marked bold; they were disabled before.
Signatures changed. ViewCommandSource gained getLabel(); an implementation outside the engine must implement it (returning null for no heading). ContextMenuContribution gained a three-argument constructor; the two-argument one is unchanged. CommandModel gained isActive() with a default of false, so implementations need no change. The record ReactMenuControl.MenuEntry gained an active component; code constructing entries through the record constructor or the five-argument item(...) factory passes it. A theme styling tlReactButtonActive uses tlReactButton--active.