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
- 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.
- 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.
- 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. - The engine ships developer articles in its jars (
META-INF/tl-docs/**/*.md). If thetl-mcptools are available, read them withread_doc(names given below asdoc:<name>); they are the authoritative reference for the new syntax. - 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-17toJavaSE-21and the compiler compliance level to 21 (.classpath,.settings/org.eclipse.jdt.core.prefs,*.launchfiles). - Jetty EE 10 → EE 11: code using
org.eclipse.jetty.ee10.*/ artifactsjetty-ee10-*must switch toorg.eclipse.jetty.ee11.*/jetty-ee11-*. A self-declaredjakarta.servlet.jsp-apimust be version4.0.x. AbstractCloseableIteratorandAbstractStatementBatchno 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-professionalis no longer a dependency oftl-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'sScriptTask.
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
TestAlland 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
bosis gone: a build with-P bosno longer enables LDAP (Maven 3.9 only warns about the unknown profile). Replacement:-P with-ldap, with the LDAP settings andldap_credential(plain value) from the environment; for DeepL settranslation_api_keyandtranslation_enabled=true. - The DB profiles of
tl-parent-app(with-h2,with-mysql, …) now settl.app.metaConfAddons.dbinstead oftl.app.metaConfAddons. A build that selected the database directly viatl.app.metaConfAddons(e.g.-Dtl.app.metaConfAddons=db-h2.xmlor a POM property) must settl.app.metaConfAddons.dbinstead; otherwise the defaultdb-jndi.xmlis 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):
- Remove
<parent>(tl-parent-app/tl-parent-core); declare<groupId>/<version>explicitly if inherited. - Set
<packaging>tl-app</packaging>(fragment:tl-fragment) and declaretl-maven-pluginwith<version>${tl.version}</version>and<extensions>true</extensions>under<build><plugins>. - Add property
tl.versionand importtl-bomindependencyManagement(<type>pom</type>,<scope>import</scope>,<version>${tl.version}</version>). BOM and plugin version must match (build fails otherwise). - Overridden version properties (e.g.
jetty.version) no longer work; declare that library with a version in the owndependencyManagement. - Declare repositories
tl-releases/tl-snapshotsasrepositoryandpluginRepositoryunless Maven settings provide them. - Delete
jdeb/upload-maven-pluginblocks andtl.deb.*properties that only repeat defaults. - 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 ofmetaConf.txt,theme.xml, theme settings and merged view tabs changes). - Verify: compare
mvn help:effective-pomand the content oftarget/<finalName>-app.warbefore/after. Seedoc: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 hooksecdemo:<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 (aStartupAction), positioned after the corresponding hook (config:position="after" config:reference="<id>"). Hook optionon-error="fail|log-error|log-warn"(defaultfail). - 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
DBPropertiesare 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 settl_pac_secret(otherwise no Keycloak login). - Local development with H2: add
devel-secrets.xmlafterdevel-h2.xmlindeploy/local/webapp/WEB-INF/conf/metaConf.txt. Without it,h2_passwdis read only from the environment and the existing DB./tmp/h2(created with passwordpassword) rejects the login. Personal keys (e.g.translation_api_key) can go to files in~/.top-logic/secretsor 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 viaoverridesor explicit<… class="com.top_logic.basic.credential.EnvironmentSecretSource" name="…"/>. - Own services reading a named secret declare
@ServiceDependencies(SecretStoreService.Module.class); their tests startSecretStoreService. - MCP client:
bearer-token-env-varis removed. Replacement:
An<credentials class="com.top_logic.basic.credential.HeaderCredentials" scheme="Bearer"> <secret name="MY_MCP_TOKEN"/> </credentials>HttpMcpTransportneedsmcp-json-jackson3on the classpath (comes withtl-ai-mcp-server). - A2A remote agent:
token-envis removed; samecredentialsform. - Vector index:
mistral-api-key="%MISTRAL_API_KEY%"→<mistral-api-key name="tl_mistral_api_key"/>. - Java API changes:
DataSourceConfig.getDriverOptions()returnsMap<String, AbstractDriverOption>;ServiceProviderInfo.createFromConfig(Config)andwithCredentials(String); getters of converted secret properties returnPolymorphicConfiguration<? extends SecretSource>; an element withoutclassreads asNamedSecretSource.Config(notEnvironmentSecretSource.Config);GiteaIssueTool.TOKEN_SECRETinstead ofTOKEN_VARIABLE;Pac4jLoginMethodreceives itsClientConfiguratorin the constructor;ReflectiveToolFactoryaccepts<tool class=…>; OpenAPI client:HTTPAuthenticationEnhancer,ClientCredentialEnhancer,TokenBasedEnhancerremoved;LLMUpstream(tl-factory) carries aCredentialProviderinstead of the API key,LLMUpstream.fromProvidertakes the startedAnthropicProviderConfig(LLMProviderService#getProvider(String));ExternalAuthenticationServlet.forwardToSSOLoginFailedtakes aResKey; constructorLoginMethodsElement(String, Supplier, Supplier)gets an additional error-message source;I18NConstants.ERROR_NTLM_AUTHEMTICATION_FAILED(com.top_logic.base.accesscontrol) is replaced byERROR_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-mailsis removed → delete it (left over → configuration error at startup). - Default
strategy-for-unknown-mailsis nowKEEP(mails stay in INBOX). If the application relied on deletion, setstrategy-for-unknown-mails="DELETE"explicitly. - First run after the update processes only mails arriving afterwards (
initial-mails="SKIP"). To process existing mails once, setinitial-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 rulesuser/superuser) remove those entries. - Applications with an own default group (e.g.
users) will additionally getuser. Recommended: rename the own group touser. - Mandatory if a group differs from
user/superuseronly 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:- Remove
default-groupand the own group from the InitialGroupManager configuration. - Change references (e.g.
<group-with-name name="users"/>in role rules) touser. - 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> - If the application had no own migrations so far, add its migration module to
minimum-modulesof theMigrationService(otherwise the module counts as new and its migrations are skipped; see also #29979 below). Example: tl-demo-react, migrationTicket_30051_rename_group_users_to_user.
- Remove
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>withoutconfig:overridenow additionally get the shipped providersanthropic,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
LLMProviderServiceconfig and provide keys astl_<provider>_api_key. - Without
default-model, an agent now runs on the first model of the first available provider. - Property
llm-configurationof the model catalog is removed; delete<model-catalog><llm-configuration>…</llm-configuration></model-catalog>(it never had an effect), also inWEB-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-lastautomatically on next start. To keep the old layout setbranch-last="false"on the type systemDefault. - Optional: an application without own branches may add
top-logic.singleBranch.config.xmlto itsmetaConf.txt; branch columns are removed on next start (only if the DB contains only trunk). - An own declaration of
singleBranchMeta.xmlin the type system configuration is no longer needed. - Changing
multiple-branchesorbranch-lastis 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
idmust 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.xmlmust carry anid. - 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 isaccount-menu(before:account). Overlays addressing them useconfig: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 withoutobject). Declare the operation in<security-config>; the role profiles assigned to the scope become<grant>entries of that operation. The<scopes>configuration ofSecurityScopeServiceand scope entries insecurity.xmlare 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'sinputs; omitobjectwhen 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:
A grant of an undeclared custom operation is a configuration error at startup. Built-in operations<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>Read,Write,Create,Delete,Discover,Exportmust not be declared. Each operation needs labelscommandGroup.<Name>/commandGroup.<Name>.tooltipin a resource bundle. - Entering edit mode of a form now requires
Writeon the form object unlessedit-operationsays 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-scriptwhosesecurity-objectyields 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/inPersonContextnow 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:
input→inputson commands:input="ch"on a command becomesinputs="ch"; rename withinputs="selection as ticket, other"or<input channel="selection" as="ticket"/>.inputon other elements and on<drag>/<drop>stays a single channel (now with optionalas).- 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 inDisplayTargetServiceconfigurations. Use$<name>variables:- command
inputschannels →$<channel name>(or theasname); - default names of values supplied by triggers/elements: table row activation
row, tree nodenode,value-inputsubmitvalue, HTML linklink, diagram clickelement, uploaded filefile; drop:objectsplustarget/before/parent(renamable:as,target-as,parent-as,before-as); command without inputs and trigger value:input; - tables:
row(row-as), column cell valuevalue(value-as), dynamic columncolumn(column-as), fixed:members,left/right,params; trees:node(node-as); object-list / kanban cardselement(element-as), board columncolumn,on-reorderalsoobjects; calendarentry(entry-as),start/end/allDay/title; wizardstep(step-as); switch cases seevalue(as); job scripts seejob(blocks alsostate,chunk); derived channelreverseseesvalue; route parameters by their name,reversethe URL text asvalue. - Examples:
objects -> target -> …→ expression over$objects,$target;row -> …inrow-class→$row;node -> …inchildren→$node;t -> $t.…withinputs="selectedTicket"→$selectedTicket.…;s -> jobIsRunning($s)withinputs="jobState"→jobIsRunning($jobState). - An input name colliding with a supplied name is a configuration error (rename with
as/*-as).
- command
- Job scripts (
start-job): the job is$jobinstead of the first parameter. <with-result>: an action that uses the previous action's result moves into that action's<with-result>. The attributeasof an action becomes<with-result as="…">(default nameresult). 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>.- 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). <delete-object/>followed by<write-channel>to clear the selection →<write-channel … clear="true"/>.- Single-value actions (
<write-channel>,<open-dialog>,<delete-object>,<show-object>, …) takevalue="<name>"; default: innermost result, else the trigger's main argument. <open-dialog>has no result.- Behavior change to keep in mind: a command's executability rules (
<visible-if>,<disabled-if>, rules of its actions) now see all named arguments (allinputsplus the trigger value) and are re-evaluated when any input changes (before: only the first input).<bind expr>andlabel-exprof<show-view>/<show-views>see the chain's arguments; targets configured inDisplayTargetServicesee 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) && $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 anidunique 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>), andbind-input-toplus<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 withoutroute).- 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 keepview="…".
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 democomment.view.xml/comment-composer.view.xmlanddemoTicketsStoreAttachmentsindemoReactConf.config.xml. (Note: with #29781 theinitscript has no lambda head, i.e.init="$obj.get(…).map(a -> $a.get(…))".)
- only file input: a multi-valued binary attribute with
- A
<value-input>inside a<form>is now editable only in the form's edit mode; to keep it always editable setedit-modeto a channel holdingtrue. max-upload-sizenow limits each single file; limit whole multi-upload requests in the container (Jettymultipart-config). Per attribute:<max-upload-size value="…"/>annotation on attribute/type.- Automatic migration
Ticket_29852_binary_attribute_values(tl-element) creates tablesBinaryAttributeValue/BinaryAttributeValueUnversionedand<supports-multiple/>ontl.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>ofConfigControlService→<annotation-controls>. An<annotation-controls>entry for an own annotation requires@ControlAnnotationon it (else config loading fails). MultiLineTextconfiguration withrowsis removed →@MultiLine(rows = …).- Direct references to
CodeEditorControl.CPXml/CPHtml/CPText,ConfigXMLEditor.CP,MacroControlProvider(wysiwyg),EditorControlConfig,DefaultCodeEditor→@Code/@HtmlorCodeEditorControl.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-modulesof theMigrationServiceconfiguration. Since alpha12 the versions of all configured migration modules are stored on every start, and skipped modules are logged with this hint. update-classwithnew-namein another module (#29980) now also moves the class's association types; an application workaround (manual SQLUPDATE META_ELEMENT) can be removed.invert-link-tableon 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
CommitSourceand the refresh part ofAgentViewRefreshService— remove own wiring of those.
8. Verification checklist
mvn clean installof the application with JDK 21 (enable tests if possible:TestAllcontains the polymorphic-wildcard conformance check andTestViewScripts).- Start the application (local H2:
devel-secrets.xmlinmetaConf.txt, or seth2_passwd). - Read the startup log: no configuration errors (removed modules,
process-all-mails,llm-configuration, undeclared operations, staleconfig:operation="remove"). - 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. - Check custom styling of React controls visually (calendar, kanban, dashboard, media, color/icon pickers, tooltips, rich-text editor, charts).
- Check access: users without admin rights see the expected navigation entries, tabs, tiles and commands.
- Report to the user every step that was skipped, needed a decision, or touches data (group rename, SafeHTML content, mail daemon behavior).