TopLogic - the automated application engine
  • Releases
  • Dokumentation
  • Github
  • Discord
  1. Home
  2. Releases
  3. TL_8.0.0-alpha12
  4. Migration guide

8.0.0-alpha12
TopLogic Release

2026-10-30

Markdown version, e.g. for an LLM updating an application.

Migrating a TopLogic application from 8.0.0-alpha11 to 8.0.0-alpha12

Audience: an AI coding agent (or developer) upgrading a TopLogic application project. Sources: release notes https://dev.top-logic.com/tl/content/releases/TL_8.0.0-alpha12/ and the TopLogic Trac tickets flagged RequiresMigration for milestone TL_8.0.0-alpha12 (ticket numbers are given as #NNNNN; the per-ticket page is https://dev.top-logic.com/tl/content/releases/TL_8.0.0-alpha12/NNNNN/).

How to use this document

  1. Work through the sections in order. Section 1 must be done first (the project does not build otherwise); the rest can be done in any order.
  2. Every step has a Detect line: run the search in the application's source tree. If it finds nothing, the step does not apply — skip it. Do not invent changes for steps that do not apply.
  3. Many changes are reported as configuration errors at application start or when a view is loaded (not at compile time). After migrating, start the application, log in, and open every view; read the log for ConfigurationException / configuration errors.
  4. The engine ships developer articles in its jars (META-INF/tl-docs/**/*.md). If the tl-mcp tools are available, read them with read_doc (names given below as doc:<name>); they are the authoritative reference for the new syntax.
  5. Ticket descriptions on Trac are partly German; this document translates the instructions.

Search commands below assume the project root as working directory and GNU grep.


1. Build and platform

1.1 Version bump

Set the TopLogic version to 8.0.0-alpha12 (parent tl-parent-app / tl-parent-core version, or the tl.version property).

1.2 Java 21 (#29905) — mandatory

  • Build with a Java 21 JDK and run on a Java 21 runtime.
  • Eclipse projects: switch the JRE container from JavaSE-17 to JavaSE-21 and the compiler compliance level to 21 (.classpath, .settings/org.eclipse.jdt.core.prefs, *.launch files).
  • Jetty EE 10 → EE 11: code using org.eclipse.jetty.ee10.* / artifacts jetty-ee10-* must switch to org.eclipse.jetty.ee11.* / jetty-ee11-*. A self-declared jakarta.servlet.jsp-api must be version 4.0.x.
  • AbstractCloseableIterator and AbstractStatementBatch no longer close themselves on garbage collection (finalizers removed). Make sure every such iterator/batch is closed (try-with-resources).

Detect:

grep -rln --include=.classpath --include=*.prefs --include=*.launch -e 'JavaSE-17' -e '=17$' .
grep -rn -e 'jetty.ee10' -e 'jetty-ee10' -e 'jsp-api' --include=*.java --include=pom.xml --include=*.xml .
grep -rn -e 'AbstractCloseableIterator' -e 'AbstractStatementBatch' --include=*.java .

1.3 AI modules are engine modules (#29924)

Dependencies on com.top-logic:tl-ai-* / tl-chat* in version 2.1.x now use the engine version (${tl.version}); artifact IDs are unchanged.

Additional changes for applications coming from tl-ai 2.1.x (from a Trac comment on #29924, not in the release notes):

  • tl-license-professional is no longer a dependency of tl-ai-core. If the application got it transitively, declare it explicitly.
  • Secured TL-Script (also in AI tools) returns only objects the user may discover (#29782): all(), references and inputs are filtered.
  • The prompt expression of AgentTask (tl-chat) is evaluated without security checks, like the engine's ScriptTask.

Detect: grep -rn -e 'tl-ai-' -e 'tl-chat' -e 'tl-license-professional' --include=pom.xml .

1.4 Polymorphic configuration getters use wildcards (#30037) — Java

Engine getters/setters of properties holding a polymorphic configuration now declare PolymorphicConfiguration<? extends X> (also inside List<…>, Map<…>) instead of PolymorphicConfiguration<X>.

  • Variables receiving such a getter's result: declare them as PolymorphicConfiguration<? extends X>.
  • A sub-interface redeclaring such a getter should use the wildcard type too.
  • Instantiating in a stream may need a type witness: configs.stream().<X> map(context::getInstance).
  • The application's own configuration interfaces must follow the same rule: a conformance check runs in the application's TestAll and reports violations (temporary opt-out: test-polymorphic-configuration-wildcards="false" in the global test configuration).
  • XML configurations and data are unchanged.

Detect: compile errors after the bump; plus grep -rn 'PolymorphicConfiguration<[A-Z]' --include=*.java src

1.5 Development start: WebSocket container (#29898)

tl-ide-jetty now provides a Jakarta WebSocket container. Remove workarounds (e.g. an exec dependency on jetty-ee10-websocket-jakarta-server plus a web.xml listener calling Jetty's initializer by name). Register endpoints through com.top_logic.util.WebSocketEndpointListener with the context parameter websocket.endpoints in web.xml. Endpoints that relied on automatic discovery of @ServerEndpoint classes (Tomcat) must be listed in that parameter to be served by the development start too.

Detect: grep -rn -e 'websocket' -e '@ServerEndpoint' --include=*.xml --include=*.java .

1.6 Build profiles and database selection (#29948)

  • Profile bos is gone: a build with -P bos no longer enables LDAP (Maven 3.9 only warns about the unknown profile). Replacement: -P with-ldap, with the LDAP settings and ldap_credential (plain value) from the environment; for DeepL set translation_api_key and translation_enabled=true.
  • The DB profiles of tl-parent-app (with-h2, with-mysql, …) now set tl.app.metaConfAddons.db instead of tl.app.metaConfAddons. A build that selected the database directly via tl.app.metaConfAddons (e.g. -Dtl.app.metaConfAddons=db-h2.xml or a POM property) must set tl.app.metaConfAddons.db instead; otherwise the default db-jndi.xml is added.

Detect: grep -rn -e 'metaConfAddons' -e '<id>bos</id>' -e '-Pbos' -e '-P bos' .

1.7 Optional: project without TopLogic parent POM (#29956)

Not required — projects inheriting tl-parent-app (fragments: tl-parent-core) build unchanged. Only do this if asked. Switching steps (in the project POM):

  1. Remove <parent> (tl-parent-app / tl-parent-core); declare <groupId>/<version> explicitly if inherited.
  2. Set <packaging>tl-app</packaging> (fragment: tl-fragment) and declare tl-maven-plugin with <version>${tl.version}</version> and <extensions>true</extensions> under <build><plugins>.
  3. Add property tl.version and import tl-bom in dependencyManagement (<type>pom</type>, <scope>import</scope>, <version>${tl.version}</version>). BOM and plugin version must match (build fails otherwise).
  4. Overridden version properties (e.g. jetty.version) no longer work; declare that library with a version in the own dependencyManagement.
  5. Declare repositories tl-releases / tl-snapshots as repository and pluginRepository unless Maven settings provide them.
  6. Delete jdeb / upload-maven-plugin blocks and tl.deb.* properties that only repeat defaults.
  7. Optionally replace module lists by starters tl-starter-react, tl-starter-classic, tl-starter-ai, tl-starter-ai-search (independent fragments may swap order → only the merge order of metaConf.txt, theme.xml, theme settings and merged view tabs changes).
  8. Verify: compare mvn help:effective-pom and the content of target/<finalName>-app.war before/after. See doc:application-project (section "Switching a project from tl-parent-app to the packaging").

Also from #29956 (applies to everyone): diagram-js stylesheets moved. /style/diagram-js.css is now /style/umljs/diagram-js.css resp. /style/bpmn-js/diagram-js.css, scoped to their own diagrams. Applications with own .djs-* rules or a direct reference to /style/diagram-js.css must check them.

Detect: grep -rn -e 'diagram-js.css' -e '\.djs-' .


2. Server configuration (*.config.xml, metaConf.txt, security.xml)

2.1 Setup services became startup hooks (#29944) — mandatory if referenced

These modules no longer exist; remove any <module key="…$Module" …/> entry naming them (ModuleSystem configuration): InitialDataSetupService$Module, AccessConfigurationSetupService$Module, InitialProcessSetupService$Module, ImportDocumentationModule$Module, AgentNameCheck$Module, SecDemoSetupService$Module.

Old service New action class (hook) Hook id
com.top_logic.knowledge.service.db2.InitialDataSetupService com.top_logic.knowledge.service.db2.InitialDataSetup initial-data
com.top_logic.tool.boundsec.config.AccessConfigurationSetupService com.top_logic.tool.boundsec.config.AccessConfigurationSetup access-configuration
com.top_logic.bpe.execution.engine.InitialProcessSetupService com.top_logic.bpe.execution.engine.InitialProcessSetup initial-workflows
com.top_logic.doc.ImportDocumentationModule com.top_logic.doc.ImportDocumentation documentation
com.top_logic.tl.ai.core.security.AgentNameCheck (service) same class as action agent-name-check
com.top_logic.ai.demo.secdemo.SecDemoSetupService com.top_logic.ai.demo.secdemo.SecDemoSetup secdemo
  • A service that was switched off (value="false") → remove the hook instead:
    <config service-class="com.top_logic.util.StartupHooks">
      <instance>
        <hooks>
          <hook id="initial-workflows" config:operation="remove"/>
        </hooks>
      </instance>
    </config>
    
  • Overriding the security demo config (<config service-class="com.top_logic.ai.demo.secdemo.SecDemoSetupService">) → configure the action of hook secdemo:
    <config service-class="com.top_logic.util.StartupHooks">
      <instance>
        <hooks>
          <hook id="secdemo">
            <action class="com.top_logic.ai.demo.secdemo.SecDemoSetup" initial-password="..."/>
          </hook>
        </hooks>
      </instance>
    </config>
    
  • An own service that waited (@ServiceDependencies) for one of these setup services to see the imported data must itself become a startup hook (a StartupAction), positioned after the corresponding hook (config:position="after" config:reference="<id>"). Hook option on-error="fail|log-error|log-warn" (default fail).
  • Java code using static helpers/constants moves to the new class names (e.g. InitialProcessSetupService.importWorkflow() → InitialProcessSetup.importWorkflow(), InitialDataSetupService.FILE_SUFFIX → InitialDataSetup.FILE_SUFFIX).
  • Already imported data is not re-imported (markers in DBProperties are unchanged).

Detect:

grep -rn -e 'InitialDataSetupService' -e 'AccessConfigurationSetupService' -e 'InitialProcessSetupService' \
  -e 'ImportDocumentationModule' -e 'AgentNameCheck' -e 'SecDemoSetupService' .

2.2 Secrets / credentials (#29948)

Existing configurations with attribute or element content (api-key="unencrypted:…", encrypted values, aliases) still load. To change:

  • These aliases are removed: %H2_PASSWD%, %DB2_PASSWD%, %MSSQL_PASSWD%, %MYSQL_PASSWD%, %ORACLE_PASSWD%, %POSTGRESQL_PASSWD%, the truststore/keystore aliases of MSSQL/MySQL, %LDAP_CREDENTIAL%, %SMTP_PASS%, %IMAP_PASS%, %PAC_SECRET%, %BLOB_CONNECT%, %BLOB_USER%, %BLOB_PASS%. Engine configs now reference named secrets; the environment variables of the same name stay valid: h2_passwd, db2_passwd, mssql_passwd, mysql_passwd, oracle_passwd, postgresql_passwd, ldap_credential, smtp_pass, imap_pass, tl_pac_secret, blob_connect. If the application defined or used one of the aliases, switch to the named form, e.g. <encrypted-option name="password"><value name="h2_passwd"/></encrypted-option>.
  • No default values any more. An installation with H2 relying on the default password must set h2_passwd; one using the pac4j demo client must set tl_pac_secret (otherwise no Keycloak login).
  • Local development with H2: add devel-secrets.xml after devel-h2.xml in deploy/local/webapp/WEB-INF/conf/metaConf.txt. Without it, h2_passwd is read only from the environment and the existing DB ./tmp/h2 (created with password password) rejects the login. Personal keys (e.g. translation_api_key) can go to files in ~/.top-logic/secrets or stay env vars.
  • Environment variables contain the plain value (unencrypted: prefix still accepted; encrypted values in variables are not supported).
  • Other secret stores (directory, Key Vault): configure SecretStoreService. Exceptions per secret via overrides or explicit <… class="com.top_logic.basic.credential.EnvironmentSecretSource" name="…"/>.
  • Own services reading a named secret declare @ServiceDependencies(SecretStoreService.Module.class); their tests start SecretStoreService.
  • MCP client: bearer-token-env-var is removed. Replacement:
    <credentials class="com.top_logic.basic.credential.HeaderCredentials" scheme="Bearer">
      <secret name="MY_MCP_TOKEN"/>
    </credentials>
    
    An HttpMcpTransport needs mcp-json-jackson3 on the classpath (comes with tl-ai-mcp-server).
  • A2A remote agent: token-env is removed; same credentials form.
  • Vector index: mistral-api-key="%MISTRAL_API_KEY%" → <mistral-api-key name="tl_mistral_api_key"/>.
  • Java API changes: DataSourceConfig.getDriverOptions() returns Map<String, AbstractDriverOption>; ServiceProviderInfo.createFromConfig(Config) and withCredentials(String); getters of converted secret properties return PolymorphicConfiguration<? extends SecretSource>; an element without class reads as NamedSecretSource.Config (not EnvironmentSecretSource.Config); GiteaIssueTool.TOKEN_SECRET instead of TOKEN_VARIABLE; Pac4jLoginMethod receives its ClientConfigurator in the constructor; ReflectiveToolFactory accepts <tool class=…>; OpenAPI client: HTTPAuthenticationEnhancer, ClientCredentialEnhancer, TokenBasedEnhancer removed; LLMUpstream (tl-factory) carries a CredentialProvider instead of the API key, LLMUpstream.fromProvider takes the started AnthropicProviderConfig (LLMProviderService#getProvider(String)); ExternalAuthenticationServlet.forwardToSSOLoginFailed takes a ResKey; constructor LoginMethodsElement(String, Supplier, Supplier) gets an additional error-message source; I18NConstants.ERROR_NTLM_AUTHEMTICATION_FAILED (com.top_logic.base.accesscontrol) is replaced by ERROR_EXTERNAL_LOGIN_FAILED.
  • Full before/after examples: doc:credentials/migration, doc:credentials/secret-stores.

Detect:

grep -rn -E '%(H2|DB2|MSSQL|MYSQL|ORACLE|POSTGRESQL)_PASSWD%|%LDAP_CREDENTIAL%|%SMTP_PASS%|%IMAP_PASS%|%PAC_SECRET%|%BLOB_(CONNECT|USER|PASS)%|%MISTRAL_API_KEY%' .
grep -rn -e 'bearer-token-env-var' -e 'token-env' -e 'devel-h2.xml' .

2.3 Mail server daemon (#30030)

  • Option process-all-mails is removed → delete it (left over → configuration error at startup).
  • Default strategy-for-unknown-mails is now KEEP (mails stay in INBOX). If the application relied on deletion, set strategy-for-unknown-mails="DELETE" explicitly.
  • First run after the update processes only mails arriving afterwards (initial-mails="SKIP"). To process existing mails once, set initial-mails="PROCESS".

Detect: grep -rn -e 'process-all-mails' -e 'MailServerDaemon' -e 'strategy-for-unknown-mails' .

2.4 Initial groups user / superuser (#30051)

tl-element (securityStructureGroups.config.xml) now provides to every application: groups user (default group of new accounts) and superuser, roles SecurityStructure.user / SecurityStructure.superuser, role rules with ids user / superuser on SecurityStructure#ROOT. Created at start if missing; no data migration.

  • Applications that copied this from tl-bpe-app (InitialGroupManager groups, InitialRolesManager roles, AccessManager role rules user/superuser) remove those entries.
  • Applications with an own default group (e.g. users) will additionally get user. Recommended: rename the own group to user.
  • Mandatory if a group differs from user/superuser only in case (User, Superuser, SuperUser): on MySQL/MariaDB and MS SQL Server startup fails (unique index), on H2/PostgreSQL/ Oracle/DB2 two groups coexist. Rename procedure:
    1. Remove default-group and the own group from the InitialGroupManager configuration.
    2. Change references (e.g. <group-with-name name="users"/> in role rules) to user.
    3. Rename the existing group with a migration that runs before the first start on the new version:
      <update-object no-type-column="true" table="Group" type="tl.accounts:Group">
        <key-values>
          <key-value column="NAME" column-type="String" value="users"/>
        </key-values>
        <values>
          <value column="NAME" column-type="String" value="user"/>
        </values>
      </update-object>
      
    4. If the application had no own migrations so far, add its migration module to minimum-modules of the MigrationService (otherwise the module counts as new and its migrations are skipped; see also #29979 below). Example: tl-demo-react, migration Ticket_30051_rename_group_users_to_user.

Detect: grep -rn -i -e 'InitialGroupManager' -e 'default-group' -e 'group-with-name' -e 'SecurityStructure\.\(user\|superuser\)' .

2.5 AI: LLM providers (#29965) and model catalog (#29954)

  • LLMProviderService: own <providers> without config:override now additionally get the shipped providers anthropic, openai, mistral, xai (an own provider of the same name is merged). To keep exactly the own list: <providers config:override="true">. If an own and a shipped provider expose the same model, startup fails unless every reference names a provider (default-provider / provider) or the list is replaced.
  • An application that rebuilt the providers only for the demo defaults may delete its LLMProviderService config and provide keys as tl_<provider>_api_key.
  • Without default-model, an agent now runs on the first model of the first available provider.
  • Property llm-configuration of the model catalog is removed; delete <model-catalog><llm-configuration>…</llm-configuration></model-catalog> (it never had an effect), also in WEB-INF/autoconf/com.top_logic.tl.ai.core.provider.LLMProviderService.config.xml. Otherwise the application does not start.

Detect: grep -rn -e 'LLMProviderService' -e 'llm-configuration' -e '<providers' .

2.6 H2 / single branch (#29876) — no mandatory steps

  • H2 databases are switched to branch-last automatically on next start. To keep the old layout set branch-last="false" on the type system Default.
  • Optional: an application without own branches may add top-logic.singleBranch.config.xml to its metaConf.txt; branch columns are removed on next start (only if the DB contains only trunk).
  • An own declaration of singleBranchMeta.xml in the type system configuration is no longer needed.
  • Changing multiple-branches or branch-last is migrated automatically at startup (multiple → single requires that only the trunk exists, else the migration aborts). On Oracle/MySQL, DDL is not transactional: back up the database before switching.

2.7 config:ref and globally unique view ids (#29896)

  • An id must be unique in the whole view (not only among siblings). Duplicate ids of elements, tabs, panes, cases, sidebar items or menu groups → view is rejected when loaded.
  • Every <tab> of a <tab-bar> and every <section> of an <accordion> in a .view.xml must carry an id.
  • Renamed ids in shipped views: inner tab bars of admin sections are now access-control-views, monitoring-views, system-views (before: views); the user-menu menu is account-menu (before: account). Overlays addressing them use config:ref: <tab-bar config:ref="system-views">, <menu config:ref="account-menu">.
  • An overlay that contributed an anonymous (key-less) entry to a keyed list and relied on merging into the base's anonymous entry now appends a second entry → give the base entry an id and address it.
  • See doc:view-layer/overlays.

Detect: grep -rn -e '<tab ' -e '<section ' --include=*.view.xml . (check for missing id), and overlays of engine views (same path as an engine view under WEB-INF/views/).


3. Security / access checks

3.1 Consolidated access checks in views and commands (#29856) — React UI

  • <access-control scope="administration"/> → <access-control operation="Administration"/>.
  • An application scope becomes an operation on the type of the object the unit shows: <access-control operation="X" inputs="…" object="…" type="…"/> (or on the security root without object). Declare the operation in <security-config>; the role profiles assigned to the scope become <grant> entries of that operation. The <scopes> configuration of SecurityScopeService and scope entries in security.xml are removed.
  • Command executability:
    • <model-access operation="X" object="ch"/> → <access><operation name="X" object="$ch" type="…"/></access> on the command (add the channel to the command's inputs; omit object when the checked object is the one of the enclosing unit).
    • <model-access operation="Create" type="T" [container="ch" reference="r"]/> → <access><create type="T" [in="$ch" reference="r"]/></access>.
    • <security group="X" …/> → an <operation> check.
  • Custom operations used in grants, views, commands or privileged scripts must be declared on the type:
    <security-config>
      <class name="my.module:MyType">
        <operations>
          <operation name="Show" implied-by="Read"/>
          <operation name="Approve"/>
        </operations>
        <grant operation="Approve" roles="my.Role"/>
      </class>
      <singleton name="SecurityStructure#ROOT">
        <operations><operation name="Administration"/></operations>
        <grant operation="Administration" roles="Administrator"/>
      </singleton>
    </security-config>
    
    A grant of an undeclared custom operation is a configuration error at startup. Built-in operations Read, Write, Create, Delete, Discover, Export must not be declared. Each operation needs labels commandGroup.<Name> / commandGroup.<Name>.tooltip in a resource bundle.
  • Entering edit mode of a form now requires Write on the form object unless edit-operation says otherwise; an <edit-executability> with <model-access operation="Write"/> can be dropped.
  • <delete-object> on an empty input is disabled; a command running <enter-edit-mode/> is hidden/disabled by the form's edit permission.
  • Access checks of commands running <link-element/> / <remove-element/> are declared once on the <object-list> (<link-access> / <remove-access>).
  • Grants of command groups only checked by the classic UI do not belong in <security-config> (the coverage check reports them as unchecked grants).
  • A privileged-script whose security-object yields a collection is now allowed when the user holds the command group on every element (was always denied).
  • See doc:access-configuration.

Detect:

grep -rn -e '<access-control' -e '<model-access' -e '<security ' -e 'SecurityScopeService' -e '<scopes' \
  -e 'edit-executability' -e '<link-element' -e '<remove-element' .

3.2 Thread context / security bypass API (#29798) — Java

Removed API and replacements:

Removed Use instead
ThreadContextManager.inInteraction(String, …), ThreadContextManager.inSystemInteraction(Class, …) ThreadContext.inSystemContext(Class, …)
TLContextManager.inRootPersonContext(…) ThreadContext.inSystemContext(Class, …) or TLContextManager.inPersonContext(PersonManager.getManager().getRoot(), …)
ThreadContext.pushSuperUser() / popSuperUser() / resetSuperUser(), ModelAccessRights.uncheckedSecurity(…) ThreadContext.withoutSecurityChecks(…) (keeps the person) or ThreadContext.inSystemContext(…) (no person)
TLContext.setCurrentPerson(…), switching users via setPerson(…) / setContextId(…) TLContextManager.inPersonContext(person, …)
protected hook ThreadContext#isAdminContext() rename override to isUserAdministrator(); ask ThreadContext.isAdmin() (also instead of TLContext.isAdmin())

Behavior changes:

  • inSystemContext / inPersonContext now also switch identity inside an existing interaction. A job must not log in or set a person meant to outlive it: look up the account inside the job, return it, continue outside.
  • Setting a person no longer switches security checks on/off. Without a person and without bypass, access is denied.
  • JUnit tests now run in the system context; tests asserting access denials must run the checked part in TLContextManager.inPersonContext(person, …).

Detect:

grep -rn -e 'inInteraction(' -e 'inSystemInteraction(' -e 'inRootPersonContext' -e 'pushSuperUser' \
  -e 'popSuperUser' -e 'resetSuperUser' -e 'uncheckedSecurity' -e 'setCurrentPerson' -e 'isAdminContext' \
  -e 'TLContext.isAdmin' --include=*.java .

4. React views (*.view.xml)

4.1 Action chains with named arguments (#29781) — biggest change

Concept: a trigger provides named arguments; scripts use them as variables without a lambda head; actions no longer pass their result to the next action — follow-up actions that need the result go into <with-result>. A remaining lambda head in a view script is a configuration error when the view is loaded (a per-module test TestViewScripts checks all *.view.xml).

Steps:

  1. input → inputs on commands: input="ch" on a command becomes inputs="ch"; rename with inputs="selection as ticket, other" or <input channel="selection" as="ticket"/>. input on other elements and on <drag>/<drop> stays a single channel (now with optional as).
  2. Remove lambda heads in all view scripts (action chains, refuse-if, <execute-script>, if, switch/case, confirm, notify, download, scroll-to-anchor, start-job, evaluate-script, executability rules, derived channels, row-class, columns, children, …) and in DisplayTargetService configurations. Use $<name> variables:
    • command inputs channels → $<channel name> (or the as name);
    • default names of values supplied by triggers/elements: table row activation row, tree node node, value-input submit value, HTML link link, diagram click element, uploaded file file; drop: objects plus target / before / parent (renamable: as, target-as, parent-as, before-as); command without inputs and trigger value: input;
    • tables: row (row-as), column cell value value (value-as), dynamic column column (column-as), fixed: members, left/right, params; trees: node (node-as); object-list / kanban cards element (element-as), board column column, on-reorder also objects; calendar entry (entry-as), start/end/allDay/title; wizard step (step-as); switch cases see value (as); job scripts see job (blocks also state, chunk); derived channel reverse sees value; route parameters by their name, reverse the URL text as value.
    • Examples: objects -> target -> … → expression over $objects, $target; row -> … in row-class → $row; node -> … in children → $node; t -> $t.… with inputs="selectedTicket" → $selectedTicket.…; s -> jobIsRunning($s) with inputs="jobState" → jobIsRunning($jobState).
    • An input name colliding with a supplied name is a configuration error (rename with as/*-as).
  3. Job scripts (start-job): the job is $job instead of the first parameter.
  4. <with-result>: an action that uses the previous action's result moves into that action's <with-result>. The attribute as of an action becomes <with-result as="…"> (default name result). Follow-up actions after <if>/<switch> using their result move into the <with-result> of the respective result action inside. Follow-ups after <with-transaction> using its result move into its <with-result>.
  5. UI-changing actions inside <with-transaction> (write channel, open/close dialog, navigation, messages, confirmations, downloads, …) move into the transaction's <with-result> (else config error).
  6. <delete-object/> followed by <write-channel> to clear the selection → <write-channel … clear="true"/>.
  7. Single-value actions (<write-channel>, <open-dialog>, <delete-object>, <show-object>, …) take value="<name>"; default: innermost result, else the trigger's main argument.
  8. <open-dialog> has no result.
  9. Behavior change to keep in mind: a command's executability rules (<visible-if>, <disabled-if>, rules of its actions) now see all named arguments (all inputs plus the trigger value) and are re-evaluated when any input changes (before: only the first input). <bind expr> and label-expr of <show-view>/<show-views> see the chain's arguments; targets configured in DisplayTargetService see the shown object as $object.

Examples:

<drop accept="node" target="ordered" as="nodes" parent-as="folder" before-as="next"
      refuse-if="$folder.recursion(p -> $p.container()).containsSome($nodes)">
  <execute-script function="… $folder.add(…, $nodes) …"/>
</drop>

<generic-command inputs="model">
  <store-form-state/>
  <persist-transient container="container" reference="milestones">
    <with-result>
      <write-channel name="selection"/>
      <close-dialog/>
    </with-result>
  </persist-transient>
</generic-command>

<with-transaction>
  <persist-transient>
    <with-result as="created">
      <execute-script function="$created.set(demo.tickets:Ticket#status, demo.tickets:TicketStatus#closed)"/>
    </with-result>
  </persist-transient>
  <with-result>
    <write-channel name="selection"/>
  </with-result>
</with-transaction>

<generic-command inputs="selection">
  <delete-object/>
  <write-channel name="selection" clear="true"/>
</generic-command>

<table inputs="filterState as state" row-as="ticket"
       row-class="if($ticket.get(demo.tickets:Ticket#done) &amp;&amp; $state == 'all', 'tl-status-info', null)">

Note: lambdas inside an expression (e.g. recursion(p -> …), map(m -> …)) stay; only the leading parameter head of the whole script is removed.

Java API: ViewActionChain.run/nest take ActionArguments; ViewCommand.Config#getInput() removed (use getInputs()); WithResultActions has getWithResult() (WithResult with getAs(), getActions()) instead of getAs()/getActions(); Inputs#getInputs() returns List<InputRef> instead of List<ChannelRef>; ViewExecutabilityRule has additional isExecutableFor(ActionArguments); a non-instantiable rule is now a configuration error.

Reference: doc:view-layer/channels (section "Which names a script sees").

Detect:

grep -rln --include=*.view.xml -E '(expr|function|refuse-if|row-class|children|init|reverse|label-expr|visible-if|disabled-if)="[a-zA-Z_][a-zA-Z0-9_]* *->' .
grep -rn --include=*.view.xml -e ' input="' -e ' as="' .

4.2 Tile stack frames and navigate-push (#29942 description, migration section)

  • Every <frame> of a <tile-stack> needs an id unique in the stack: <frame id="person" route="person/:person" view="…">.
  • <navigate-push view="path/x.view.xml" …> → <navigate-push frame="<frame id>" …>. The push must name a successor of the frame it is shown in (from the start view: a top-level <frame>), and bind-input-to plus <bind> must cover exactly the frame's parameters (route placeholders and <param>s). For a value a frame receives without placeholder, declare <param name="x"/>. Every frame that is pushed must be declared (if needed without route).
  • Nest <frame> declarations the way frames are stacked; for recursion/reuse use <frame-ref frame="<id>"/>. A flat list now means: each frame follows only the start view.
  • Display targets, <show-view>, <show-views>: a step that navigates into a tile stack becomes <show frame="<id>"> (bindings must cover the frame's parameters). Steps revealing embedded views keep view="…".

Detect: grep -rn -e '<tile-stack' -e '<frame' -e '<navigate-push' -e '<show-view' -e '<show-views' -e 'DisplayTargetService' .

4.3 <file-list> removed; multi-valued binaries (#29852)

  • <file-list attribute="…"/> no longer exists. Replacements:
    • only file input: a multi-valued binary attribute with <field attribute="…"/> (persisted), or without model attribute <value-input multiple="true" type="tl.core:Binary" value="ch"/> plus a command processing the files;
    • files stored as own composition objects: fill a channel (<channel name="files" inputs="obj" init="…"/>) and reconcile on save via script: <save-action> with <with-transaction><store-form-state/><execute-script …/></with-transaction><leave-edit-mode/>, <cancel-action> with <discard-form-state/> and refilling the channel. Template: React demo comment.view.xml / comment-composer.view.xml and demoTicketsStoreAttachments in demoReactConf.config.xml. (Note: with #29781 the init script has no lambda head, i.e. init="$obj.get(…).map(a -> $a.get(…))".)
  • A <value-input> inside a <form> is now editable only in the form's edit mode; to keep it always editable set edit-mode to a channel holding true.
  • max-upload-size now limits each single file; limit whole multi-upload requests in the container (Jetty multipart-config). Per attribute: <max-upload-size value="…"/> annotation on attribute/type.
  • Automatic migration Ticket_29852_binary_attribute_values (tl-element) creates tables BinaryAttributeValue / BinaryAttributeValueUnversioned and <supports-multiple/> on tl.core:Binary; no data migration needed.

Detect: grep -rn -e '<file-list' -e 'FileListElement' -e 'FileListControl' -e 'max-upload-size' .

4.4 route="none" (#29923)

Remove route="none" from a navigation entry or tab whose content should have deep links (the URLs shown before could not be opened anyway).

Detect: grep -rn 'route="none"' --include=*.view.xml .

4.5 View references are ViewRef (#29934) — Java only

.view.xml files need no change. Java getters/setters of view-path properties now use com.top_logic.layout.view.ViewRef instead of String (e.g. ViewConfig#getDefaultView(), ReferenceElement.Config#getView(), NavigatePushCommand.Config#getView(), OpenDialogCommand.Config#getDialogView()): path relative to /WEB-INF/views/ via getPath(), full load path via fullPath() (instead of ViewLoader.VIEW_BASE_PATH + config.getView()); set with config.setView(new ViewRef("customers/detail.view.xml")). ChildGroup.view(...), ChildGroup.EmbeddedView, OpenDialogAction(ViewRef, …) likewise. Overlays of designer.view.xml: attribute design-tree of <save-design>, <revert-design>, <select-view> is now session; <design-tree> takes session instead of input.

Detect: grep -rn -e 'getDefaultView()' -e 'getDialogView()' -e 'VIEW_BASE_PATH' -e 'ChildGroup' -e 'OpenDialogAction' -e 'design-tree' .


5. Configuration form editors: @ControlProvider / @ConfigControl removed (#29860) — Java + XML

Old New
@ControlProvider(MultiLineText.class) @MultiLine (optional rows = …)
@ControlProvider(CommonMarkText.class) @CommonMark
@ControlProvider(CodeEditorControl.CPHtml.class) / CPXml / CPText @Code(language = CodeLanguage.HTML) / XML / PLAIN
@ControlProvider(ConfigXMLEditor.CP.class) (with @PropertyEditor(ConfigXMLEditor.class)) @Code(language = CodeLanguage.XML) (keep the property editor)
@EditorControlConfig(language = "ace/mode/json") + @PropertyEditor(DefaultCodeEditor.class) @Code(language = CodeLanguage.JSON) (both removed)
@ControlProvider(StructuredTextControlProvider.class) @Html
@ControlProvider(MacroControlProvider.class) @Html(features = StructuredTextConfigService.FEATURE_SET_MACRO)
@ControlProvider(TLScriptCodeEditorControl.PlainCP.class) / CP.class on an Expr property remove (value type Expr suffices)
@ControlProvider(GroupInlineControlProvider.class) @Inline
@ControlProvider(SelectionControlProvider.class) on a selection remove (selection dialog from 20 options or for tree options)
@ControlProvider(PasswordInputControlProvider.class) @Encrypted
@ConfigControl(X.class) own domain annotation marked @ControlAnnotation + entry in <annotation-controls> of ConfigControlService (SPI ConfigAnnotationControlProvider)
application-specific ControlProvider via @ControlProvider own domain annotation with @ControlAnnotation; entry in <annotation-controls> of the EditorFactory (SPI AnnotationControlProvider) and, if also edited in React UI, in <annotation-controls> of ConfigControlService
  • XML customizations: <annotation config:interface="com.top_logic.layout.form.values.edit.annotation.ControlProvider" value="…"/> or <control-provider value="…"/> in <customizations> → tags <multi-line rows="…"/>, <common-mark/>, <code language="json"/>, <html features="…"/>, <inline/>.
  • Own <control-providers> of ConfigControlService → <annotation-controls>. An <annotation-controls> entry for an own annotation requires @ControlAnnotation on it (else config loading fails).
  • MultiLineText configuration with rows is removed → @MultiLine(rows = …).
  • Direct references to CodeEditorControl.CPXml/CPHtml/CPText, ConfigXMLEditor.CP, MacroControlProvider (wysiwyg), EditorControlConfig, DefaultCodeEditor → @Code/@Html or CodeEditorControl.CP / StructuredTextControlProvider.
  • React WYSIWYG attributes (tl.model.wysiwyg:Html) are checked with SafeHTML on save; stored content with markup outside the whitelist (e.g. <section>, custom elements) can only be saved again after removing that markup (data issue — inform the user, do not change data automatically).

Detect:

grep -rn -e 'ControlProvider' -e 'ConfigControl' -e 'EditorControlConfig' -e 'DefaultCodeEditor' \
  -e 'MultiLineText' -e 'control-provider' --include=*.java --include=*.xml .

6. Stylesheets: React controls moved to the design system (#29875, #29904, #29906, #29914)

Only relevant if the application has own CSS (or Java/TSX code) addressing the old tl* classes of React controls. tlReactControls.css is deleted. General rule: states are attributes, not classes (e.g. [aria-selected="true"], [aria-invalid="true"], [aria-busy="true"], [aria-pressed="true"], [data-tl-state~="…"]). Complete mapping tables: design system repository, docs/migration/altlast.md, sections "Welle 5a" … "Welle 5d".

Stylesheet registration (#29914): stylesheet tl-react-controls-css is gone → require tl-design-system-components (or tl-react-base) instead. tl-react-chartjs.css is gone (block tl-chart).

6.1 Inputs (#29875)

Removed classes tlColorInput*, tlIconSelect*, tlSlider*; custom properties --tlSlider-thumb, --tlSlider-fill, --tlSlider-fill-color, --tlSlider-rest-color. States: empty swatch [data-tl-state~="empty"]; selected tab/palette cell/icon cell [aria-selected="true"]; error [aria-invalid="true"]; warning [data-tl-state~="warning"]; mandatory [aria-required="true"]; read-only renders a span instead of a button.

Old New
.tlColorInput, __swatch, --noColor, --immutable .tl-swatch.tl-swatch--color, [data-tl-state~="empty"], read-only span
.tlColorInput__popup, __tabs, __tab--active .tl-popover.tl-picker, .tl-tabs, [aria-selected="true"]
.tlColorInput__palette, __paletteCell .tl-color-picker__palette, .tl-swatch.tl-swatch--color[role="option"]
.tlColorInput__mixer, __svField, __hueSlider .tl-color-picker__mixer, __field, __hue
.tlColorInput__actions, __btn--ok/cancel/reset .tl-button-bar.tl-picker__actions
.tlIconSelect, __swatch .tl-swatch
.tlIconSelect__popup, __grid, __iconCell--selected .tl-popover.tl-picker.tl-icon-picker, .tl-icon-picker__grid, __cell[aria-selected="true"]
.tlIconSelect__loading, __noResults .tl-status
.tlSlider, --error, --warning, __input, __value .tl-slider, __input[aria-invalid="true"], [data-tl-state~="warning"], __input, __value

6.2 Media (#29904)

Removed classes tlAudioRecorder*, tlAudioPlayer*, tlPhotoCapture*, tlDownload*, tlFileChips*, tlBinaryField*, tlImage*, tlPdfViewer*, tlHtml*, tlJobStatus*, tlCounter*, tl-wizard__steps, tl-wizard__step-item, tl-wizard__disc, tl-wizard__step-name. React control TLCounter removed. States: recording [aria-pressed="true"], uploading/loading [aria-busy="true"], dragging [data-tl-state~="dragover"], empty file [data-tl-state~="empty"], mirrored video [data-tl-state~="mirrored"], finished step [data-tl-state~="done"], current step [aria-current="step"].

Old New
.tlAudioRecorder, __button, --recording, --uploading, __icon .tl-upload with part Button (tl-button--danger, [aria-pressed="true"], [aria-busy="true"])
.tlAudioPlayer, __button, --playing, --loading part Button ([aria-busy="true"])
.tlPhotoCapture, __cameraBtn, __overlayContent, __overlayVideo--mirrored, __overlayToolbar .tl-upload, .tl-dialog__surface, .tl-media__frame[data-tl-state~="mirrored"], .tl-button-bar
.tlAudioRecorder__status--error, .tlPhotoCapture__status--error .tl-file-upload__status[role="alert"]
.tlDownload, --empty, __fileName, __fileName--empty .tl-file, [data-tl-state~="empty"], .tl-file__name
.tlFileChips, --dragover, __add--uploading .tl-chip-group, [data-tl-state~="dragover"], [aria-busy="true"]
.tlImage, --fit-cover, --fit-contain, __image, __placeholder .tl-media, --cover, --contain, __frame, __placeholder
.tlPdfViewer, __frame, __placeholder .tl-media, __frame, .tl-status
.tlHtml--document, __frame, __print, --thumbnail, __page, --inline .tl-overlay--fill, .tl-media--document, .tl-overlay__action, .tl-media--preview, .tl-media__frame, .tl-prose
.tlJobStatus, __header, __phases, __phase--done/active/pending, __bar, __error .tl-job-status, __header, .tl-steps--vertical, [data-tl-state~="done"] / [aria-current="step"], __bar, __error[role="alert"]
.tl-wizard__steps, __step-item, __disc, __step-name .tl-steps, __item, __disc, __name

6.3 Calendar, kanban, dashboard, value list (#29906)

Removed classes tlCal* and tlCalendar, tlKanbanBoard*, tlDashboard*, tlValueList*, tlRowCommandCell (now tl-table__cell--command, RowCommandColumn.CSS_CLASS), tl-table__hint (now tl-drop-hint), custom property --cal-ev-bg. States: selected [aria-selected="true"], today [aria-current="date"], non-working day [data-tl-state~="nonworking"], other month [data-tl-state~="other"], day with events [data-tl-state~="event"], preview [data-tl-state~="preview"], dragging [data-tl-state~="dragging"], drop [data-tl-state~="drop-before"|"drop-after"|"drop-onto"|"drop-refused"], dashboard edit [data-tl-state~="edit"], category [data-tl-role="category-N"].

Old New (calendar classes are BEM elements of .tl-calendar)
.tlCalendar, tlCalToolbar, tlCalNav, tlCalBtn, tlCalTitle, tlCalGranularity, tlCalBtn--active .tl-calendar, .tl-toolbar.tl-calendar__toolbar, part Button, __title, .tl-segmented ([aria-checked="true"])
tlCalTimeGrid, tlCalTimeHeader, tlCalDayHead, tlCalDayName, tlCalDayNum, tlCalGutter __time-grid, __head, __day-head, __weekday, __day-number, __gutter
tlCalAllDayRow, tlCalAllDayLabel, tlCalAllDayCell, tlCalAllDayEvent __all-day, __all-day-label, __all-day-cell, __event
tlCalScroll, tlCalTimeBody, tlCalHourAxis, tlCalHourLabel, tlCalColumns, tlCalCol, tlCalHourLine, tlCalWorkBand, tlCalNowLine __scroll, __time-body, __hours, __hour, __columns, __column, __hour-line, __work, __now
tlCalEvent, --c<N>, --default, --selected, --preview, --dragging, tlCalEventTime, tlCalEventTitle, tlCalResizeHandle, tlCalCreateInput, tlCalMonthCreate __event ([data-tl-role="category-N"], states above), __event-time, __event-title, __resize, .tl-field.tl-calendar__create
tlCalMonth, tlCalMonthHead, tlCalMonthWeekday, tlCalMonthBody, tlCalMonthWeek, tlCalMonthDays, tlCalMonthCell, tlCalMonthDayNum, tlCalMonthOverlay, tlCalMonthBar, tlCalChip, tlCalChipDot, tlCalChipTime, tlCalChipTitle, tlCalMore __month, __month-head, __month-weekday, __month-body, __week, __days, __day, __day-number, __overlay, __event, __chip, __chip-dot, __event-time, __event-title, __more
tlCalYear, tlCalMini, tlCalMiniTitle, tlCalMiniGrid, tlCalMiniWd, tlCalMiniDay __year, __mini, __mini-title, __mini-grid, __mini-weekday, __mini-day
.tlKanbanBoard, __column, __header, __label, __count, __cards, __card, __dropHint .tl-board, __column, __header, __label, part Badge, __cards (role="listbox"), .tl-item.tl-board__card (role="option"), .tl-popover.tl-drop-hint
.tlDashboard, --edit, __grid, __tile, __tile--action, __tileAction, __overlay, __hide .tl-dashboard, [data-tl-state~="edit"], __grid, .tl-card.tl-dashboard__tile.tl-overlay.tl-overlay--fill, __action (.tl-visually-hidden), __cover, .tl-overlay__action with part Button
.tlValueList, --inline, --block, __item, __separator, __row, __handle, __remove, __add .tl-value-list, --block, __item, .tl-select__separator, __row, part Button

Bridge API (tl-react-bridge): avatarCategory renamed to categoryOf (same mapping) — customer adapters must switch. New: textOn, isInteractiveTarget, isOperableTarget, kindAccepted, ICON_PREVIOUS, ICON_NEXT.

6.4 Foundation pieces and editors (#29914)

States: picking body[data-tl-state~="picking"], anchor flash [data-tl-anchor][data-tl-state~="flash"], dragging [data-tl-state~="dragging"], touch-drag armed [data-tl-armed], interactive tooltip [data-tl-state~="interactive"], editable compact field [data-tl-state~="edit"], editor error [aria-invalid="true"].

Old New
.tlFill .tl-fill
body.tlPick--active, #tl-pick-highlight body[data-tl-state~="picking"], .tl-pick-highlight
.tlAnchor, .tlAnchor--flash [data-tl-anchor], [data-tl-anchor][data-tl-state~="flash"]
.tlDragShield, .tlTouchDragImage, [data-touch-drag] .tl-drag-shield, .tl-drag-image, [data-tl-state~="dragging"] / [data-tl-armed]
.tl-tooltip-popover, -caption, -body, -arrow, --interactive; --tl-tt-padding .tl-tooltip, __caption, __body, __arrow, [data-tl-state~="interactive"]; no custom property
.tlCompactField*, --edit, --view .tl-compact-field, __preview, __opener; editable: .tl-field[data-tl-state~="edit"]
.tlWysiwygEditor*, .tlWysiwygToolbar*, --tl-wysiwyg-* .tl-field.tl-rich-text, content .tl-prose, toolbar .tl-toolbar.tl-toolbar--wrap
.tlReactChart* .tl-chart, .tl-chart__tooltip, .tl-chart__zoom-reset
.tlCodeEditor--error, .tlScriptEditor--error [aria-invalid="true"] (root classes tlCodeEditor, tlCodeEditorSurface, tlScriptEditor stay)

Java API: Chart.js module drops com.top_logic.layout.react.chartjs.Icons (colour palette) and the themeColors state of ReactChartJsControl (series colours come from the theme). ReactStackControl.setStateWords(String) is new.

Detect (all of section 6):

grep -rn -E 'tl(ColorInput|IconSelect|Slider|AudioRecorder|AudioPlayer|PhotoCapture|Download|FileChips|BinaryField|Image|PdfViewer|Html|JobStatus|Counter|Cal|Calendar|KanbanBoard|Dashboard|ValueList|RowCommandCell|Fill|Pick|Anchor|DragShield|TouchDragImage|CompactField|WysiwygEditor|WysiwygToolbar|ReactChart)|tl-wizard__|tl-table__hint|tl-tooltip-popover|--tl-tt-padding|--tlSlider-|--cal-ev-bg|--tl-wysiwyg-|tl-react-controls-css|tl-react-chartjs.css|avatarCategory|TLCounter|chartjs\.Icons|themeColors' \
  --include=*.css --include=*.java --include=*.ts --include=*.tsx --include=*.xml --include=*.html .

7. Further notes (no RequiresMigration flag, but relevant)

  • First own migration of an application (#29979): a migration module without a stored version in the DB is treated as new and its migrations are skipped. If the application adds its first own migration (e.g. for #30051) against an existing database, put the application module into minimum-modules of the MigrationService configuration. Since alpha12 the versions of all configured migration modules are stored on every start, and skipped modules are logged with this hint.
  • update-class with new-name in another module (#29980) now also moves the class's association types; an application workaround (manual SQL UPDATE META_ELEMENT) can be removed.
  • invert-link-table on MySQL (#29931) was destroying links (both ends became the destination). If an application ran this processor on MySQL with an earlier version, check the data.
  • Configured <defaults> (#29742) now take effect inside the application configuration — check for changed behavior if the application defines <defaults>.
  • Configuration layering (#29865): removing a list entry (config:operation="remove") whose key the underlying layers do not (or no longer) define now fails the application start → remove such stale removals.
  • Commits of other threads (#29952): windows follow foreign commits without configuration; this replaces CommitSource and the refresh part of AgentViewRefreshService — remove own wiring of those.

8. Verification checklist

  1. mvn clean install of the application with JDK 21 (enable tests if possible: TestAll contains the polymorphic-wildcard conformance check and TestViewScripts).
  2. Start the application (local H2: devel-secrets.xml in metaConf.txt, or set h2_passwd).
  3. Read the startup log: no configuration errors (removed modules, process-all-mails, llm-configuration, undeclared operations, stale config:operation="remove").
  4. Log in, open every React view (views are validated on load: lambda heads, ids, frames, <with-transaction> contents) and every classic layout with config forms.
  5. Check custom styling of React controls visually (calendar, kanban, dashboard, media, color/icon pickers, tooltips, rich-text editor, charts).
  6. Check access: users without admin rights see the expected navigation entries, tabs, tiles and commands.
  7. Report to the user every step that was skipped, needed a decision, or touches data (group rename, SafeHTML content, mail daemon behavior).
  • Get Started
  • Github
  • Discord
  • Das Unternehmen hinter TopLogic
  • Softwareentwicklung heute
  • Kontakt

© Copyright – Business Operation Systems GmbH

  • top-logic.com
  • Nutzungsbedingungen
  • Impressum
  • Rechtlicher Hinweis
  • Datenschutz
  • EN
  • Login