Migrating a TopLogic application from 8.0.0-alpha12 to 8.0.0-alpha13
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-alpha13/ and the
TopLogic Trac tickets flagged RequiresMigration for milestone TL_8.0.0-alpha13 (ticket numbers
are given as #NNNNN; the per-ticket page is https://dev.top-logic.com/tl/content/releases/TL_8.0.0-alpha13/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>). - Ticket descriptions on Trac are partly German; this document translates the instructions.
- Some steps delete data (#30079 comments) or change access rights (#30255). Report them to the user before starting the migrated application against a production database.
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-alpha13 (parent tl-parent-app / tl-parent-core version, or
the tl.version property).
1.2 Classic UI parts moved into separate modules (#29961) — mandatory for classic applications declaring modules individually
React applications no longer contain modules of the classic UI. Applications using tl-starter-classic,
tl-layout-formeditor or tl-chat-classic need no change; tl-addons-loginmessages brings
tl-addons-loginmessages-core itself.
A classic application that declares its modules individually and uses none of these adds, depending on the functions it uses:
| Module to add | Provides |
|---|---|
tl-model-search-classic |
model-based search, script console, Ace editor for TL-Script, code and JSON attributes and @Code configuration properties. Without it these attributes are edited without code editor, silently. |
tl-model-wysiwyg |
CKEditor for HTML attributes (before possibly obtained via tl-starter-react, tl-service-openapi-common or tl-bpe) |
tl-ai-classic |
classic AI administration (before part of tl-ai-core) |
tl-monitoring |
classic monitoring UI (before obtained via tl-layout-view) |
tl-xio-classic |
command com.top_logic.xio.command.XMLImportCommand (before part of tl-xio); class name and configuration unchanged |
Own code using classes of the classic parts (e.g. SearchSelectorComponent, TLScriptCodeEditorControl,
StructuredTextControl, StructuredTextConfigService) needs the respective classic module as
dependency; package and class names are unchanged.
Dropped transitive dependencies: tl-model-search no longer brings tl-reporting-flex (JFreeChart
reports, classes under com.top_logic.reporting.flex) and tl-layout-codeedit; tl-ai-core no
longer brings tl-chart-chartjs (Chart.js diagrams of the classic UI). An application that got these
modules only that way declares them itself or adds tl-model-search-classic resp. tl-ai-classic.
Applications with classic scripted tests (*.script.xml) not generated by the archetype declare the
test jar themselves:
<dependency>
<groupId>com.top-logic</groupId>
<artifactId>tl-layout-scripting-template</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
Detect:
grep -n -e '<artifactId>tl-' pom.xml */pom.xml 2>/dev/null
grep -rln -e 'SearchSelectorComponent' -e 'TLScriptCodeEditorControl' -e 'StructuredTextControl' \
-e 'StructuredTextConfigService' -e 'XMLImportCommand' -e 'com.top_logic.reporting.flex' \
-e 'chartjs' --include=*.java --include=*.xml src
find . -name '*.script.xml' -not -path '*/target/*' | head
(Compare the declared modules with the table; a classic application without tl-starter-classic
must check every row.)
1.3 Classic BPE task processing in tl-bpe-classic (#30065)
Classic applications that include tl-bpe directly (not via tl-bpe-modeler) and use the classic
task processing (layout templates tl-bpe/startProcess*.template.xml,
tl-bpe/advanceProcess*.template.xml, classes under com.top_logic.bpe.layout) or the PDF export add:
<dependency>
<groupId>com.top-logic</groupId>
<artifactId>tl-bpe-classic</artifactId>
</dependency>
Applications using tl-bpe-modeler or tl-bpe-app get tl-bpe-classic transitively. A React
application includes tl-bpe-view and shows the task page (bpe/tasks.view.xml) where it wants it.
Further changes affecting own dependencies and code:
tl-bpeno longer bringstl-layout-formeditor. An application using classes or layouts of the form editor that got the module only viatl-bpedeclarestl-layout-formeditoritself (or gets it viatl-bpe-classic).com.top_logic.bpe.modeler.copy.DeepCopyis nowcom.top_logic.bpe.util.DeepCopy(moduletl-bpe); adapt imports.BPMLUpdateCommand.updateCollaboration(Collaboration, Source, boolean)andBPMLUpdateCommand.updateCollaborationInTransaction(...)are removed. UseBPMLDefinitions.updateCollaboration(...)(withSourceorBinaryContent); the caller opens the transaction itself.
Detect: grep -rn -e '<artifactId>tl-bpe' -e 'startProcess' -e 'advanceProcess' -e 'com.top_logic.bpe.layout' -e 'formeditor' -e 'DeepCopy' -e 'BPMLUpdateCommand' --include=*.xml --include=*.java .
1.4 AI browser tools in the optional module tl-ai-browser (#30253)
- An application that uses the browser tools (
web-browser.*) or cookie pools adds the dependencycom.top-logic:tl-ai-browser. An existing database is taken over on startup without further steps. - An own agent that browses and names the skill
TL_webAccessadditionally names the skillTL_webBrowser. - The classes moved to the package
com.top_logic.tl.ai.browser(BrowserToolsService,BrowserTool, …) andcom.top_logic.tl.ai.browser.cookiepool(CookiePoolService); configuration and code referencing them by class name must be adapted, e.g.<config service-class="com.top_logic.tl.ai.browser.BrowserToolsService">. - An application without
tl-ai-browserwhose database already contains cookie pools keeps the tableCookiePoolunused;doc:ai/browserdescribes the migration that removes it. - An exclusion of
driver-bundlefromtl-ai-core(workaround) can be removed.
Detect: grep -rn -e 'web-browser' -e 'TL_webAccess' -e 'BrowserToolsService' -e 'BrowserTool' -e 'CookiePool' -e 'driver-bundle' .
2. Server configuration and database
2.1 Scheduler: one active/inactive switch per task (#30057) — configuration
- The task configuration property
blocked-by-defaultis removed. Replaceblocked-by-default="true"withenabled="false". - The task configuration property
blocking-allowedis removed without replacement; every task can be activated and deactivated by an administrator. - Blocking states stored in the database are taken over automatically (blocked = inactive, unblocked = active).
Java API and classic layouts: see 5.6.
Detect: grep -rn -e 'blocked-by-default' -e 'blocking-allowed' .
2.2 External login: user-mapping / user-name-extractor only in element form (#30172)
The configuration properties user-name-extractor and user-mapping (pac4j clients in
Pac4jConfigFactory, J2eeRemoteUserAuthenticationServlet) no longer accept a bare class name as
attribute value. Replace
<keycloak name="..." user-mapping="com.example.MyMapping">
by
<keycloak name="...">
<user-mapping class="com.example.MyMapping"/>
</keycloak>
The element form <user-mapping class="..." .../> with further attributes stays unchanged. Java code
reading getUserMapping() / getUserNameExtractor() from the client configuration obtains the
instances from Pac4jConfigFactory.getUserMapping(String) / getUserNameExtractor(String) instead.
Detect: grep -rn -e 'user-mapping="' -e 'user-name-extractor="' -e 'getUserMapping()' -e 'getUserNameExtractor()' .
2.3 Contact configuration (#30079)
The file WEB-INF/conf/contactConf.xml is gone; metaConf.txt of tl-contact includes
contactConf.config.xml directly. An application that overrides or references contactConf.xml in
its own metaConf.txt must adapt this.
Detect: grep -rn 'contactConf.xml' .
2.4 AI model catalog: Claude 5.5 (#30251)
- An application relying on the shipped model lists (no
tl_anthropic_models/tl_xai_models) now runs its agents onclaude-sonnet-5-5(default) resp. offersgrok-4.7,grok-4.3. To keep the previous models, set the variables (tl_anthropic_models=claude-sonnet-4-6,claude-opus-4-7,claude-haiku-4-5,tl_xai_models=grok-4.3,grok-4-1-fast). Note:grok-4-1-fastis no longer in the bundled catalog (next bullet) — declare it in<model-overrides>if you keep it. - An agent tool access policy (
AiToolAccessService) may grant only models a configured provider offers; otherwise the policy is refused and the service does not start. A policy granting a model no longer in the shipped lists (e.g.<grant models="claude-haiku-4-5"/>) changes the grant to a current model (claude-haiku-5-5) or setstl_anthropic_models/tl_xai_modelsto include it. - A model the bundled catalog no longer knows (retired models such as
claude-sonnet-4-20250514,claude-opus-4-1,grok-4-1-fast) gets no context window, output limit and prices. Switch to a current model or declare it in the<model-overrides>ofLLMProviderService. - Java code constructing
ModelInfo.ModelCost/ModelInfo.TieredCostpasses a list of tiers resp. the tier's threshold;CostCalculator.TIER_THRESHOLDis gone (ModelCost.tierFor(promptTokens)answers the applicable tier).
Detect:
grep -rn -e 'tl_anthropic_models' -e 'tl_xai_models' -e 'models=' -e 'model-overrides' -e 'claude-' -e 'grok-' \
-e 'ModelCost' -e 'TieredCost' -e 'TIER_THRESHOLD' .
2.5 Configuration variables are overridden from the environment (#30268)
A configuration variable that the application overrides with a fixed value without ${env:…}
expression (e.g. <alias name="%STORAGE_PATH%" value="./data"/>) is now still replaced by tl_<name>
from JNDI, system property or environment (here tl_storage_path). If the fixed value must win even
when the environment sets tl_<name> (e.g. a Docker image setting tl_storage_path), remove the
variable from the installation's environment or set the wanted value there.
Detect: grep -rn '<alias name="%' --include=*.xml . (then check the deployment environment for tl_<name>)
2.6 Self-service (#30041, #30183, #30223) — configuration and texts
- The login hook class
com.top_logic.security.selfservice.invitation.CreateLoginHoookis renamed toCreateLoginHook. An application referencing it (e.g. in<login-hooks>ofMainLayout$GlobalConfig) or in code must use the new name. - An application that overrides the keys
tl.security.selfservice.mail.*in its own bundle moves the texts to the optionsinvitation-text,verification-text,reset-password-text,reset-mfa-textofInvitationModule. The bundletl-security-selfservice-core.app-messagesno longer exists; a configuration referencing it drops the reference. - An application that redefines a mail script may use the configured texts, passed as fourth and fifth argument (subject, body), unfilled.
- An application with
tl-security-selfservice-corestarts theCaptchaService. If a captcha is used in a cluster, configure a shared key (<hmac-key name="tl_captcha_hmac_key"/>) and serve the application via HTTPS. - Resource keys removed from
com.top_logic.security.selfservice.invitation.I18NConstants:ERROR_NO_INVITATION__ID,UPDATED_INVITATION_CODE,DELETED_INVITATION,ERROR_CODE_MISMATCH,ERROR_CODE_MISMATCH_TOO_MANY_TIMES,REQUEST_CODE_NOT_ALLOWED__TIMEOUT. An application overriding them drops them. - Classic layouts changed in the layout editor: an application that stored its own copy of
admin/security/accounts/existingInvitations/*oradmin/security/accounts/inviteUsers/dialog.layout.xmlre-applies the new columns and commands (incl. the field / columnlanguage) to it. - Automatic: existing invitations get an expiry date (update time plus
invitation-validity) and no language (sent in the application's default language); schema (attributelocked, typeRegistration) and the form definition of the user administration are migrated at startup.
Java API: see 5.12.
Detect:
grep -rn -e 'CreateLoginHoook' -e 'tl.security.selfservice.mail' -e 'selfservice-core.app-messages' \
-e 'ERROR_NO_INVITATION' -e 'UPDATED_INVITATION_CODE' -e 'DELETED_INVITATION' -e 'ERROR_CODE_MISMATCH' \
-e 'REQUEST_CODE_NOT_ALLOWED' -e 'CaptchaService' -e 'tl-security-selfservice' .
find . -path '*layouts/admin/security/accounts*' -not -path '*/target/*'
2.7 Unique link tables and bag tables (#30031) — check own link tables
The migration Ticket_30031_unique_link_tables (tl-element) runs automatically: it creates the bag
tables, moves the links of all bag references there, removes duplicate links (same source,
metaAttribute, dest in overlapping revisions) over the whole history from the standard tables
(incl. application tables derived from them, each cleaned link is logged) and replaces the source
index by a unique index.
Applications with own link tables derived from hasWrapperAttValueBaseAssociation inherit the unique
index. If they store bag references (explicitly via <storage table="..."/>), those links are not
cleaned; the migration reports them as errors and creating the index fails on duplicates. Such
references must be reconfigured to a bag table (derived from hasWrapperAttValueBagBaseAssociation)
and their links moved with <move-links> in an own migration.
Java API: ListStorage.listConfig(...), SetStorage.setConfig(...) and
ImageGalleryStorage.imageGalleryConfig(...) have an additional parameter boolean bag (previous
behaviour: false; true for a bag reference).
Detect:
grep -rn -e 'hasWrapperAttValueBaseAssociation' -e '<storage table=' --include=*.xml .
grep -rn -e 'listConfig(' -e 'setConfig(' -e 'imageGalleryConfig(' --include=*.java .
2.8 Own migration processors: SQLFactory and DBHelper (#30163, #30167)
SQLFactory.modifyColumnName(SQLTable, String, DBType, String),modifyColumnType(SQLTable, String, DBType)andmodifyColumnMandatory(SQLTable, String, DBType, boolean)are removed. Callers pass the full definition of the column after the change:
or the variants with amodifyColumnName(table, columnName, newName, type, mandatory, binary, size, precision) modifyColumnType(table, columnName, type, mandatory, binary, size, precision) modifyColumnMandatory(table, columnName, type, mandatory, binary, size, precision)DBColumn(modifyColumnName(table, column, newName),modifyColumnType(table, column),modifyColumnMandatory(table, column)). Values formerly set via setters onSQLModifyColumn(setSize(..),setBinary(..),setPrecision(..),setMandatory(..)) go into these parameters:// before modifyColumnType(table("T"), "C", DBType.STRING).setSize(150).setBinary(true) // after modifyColumnType(table("T"), "C", DBType.STRING, mandatory, true, 150, 0)mandatoryis the mandatory flag the column shall have after the change (for a pure type change its current one).SQLFactory.addColumn(..)has no parameterdefaultValueany more;SQLAddColumn.getDefaultValue()/setDefaultValue(..)are gone. A processor adding a mandatory column with default creates the column non-mandatory, fills it withUPDATE, then sets it mandatory withSQLFactory.modifyColumnMandatory(..).- Own SQL dialects (subclasses of
DBHelper): the last parameterObject defaultValueofappendChangeColumnName(..),appendChangeColumnType(..)andappendChangeMandatory(..)is gone;appendDefaultValue(..)andappendDBType(Appendable, DBType, String, long, int, boolean, boolean, Object)no longer exist (use the variant without default value). AddDatabaseColumnProcessor:default-valueno longer creates a database default, it only fills existing rows. A permanent database default needs an ownsqlstep.
Detect:
grep -rn -e 'modifyColumnName' -e 'modifyColumnType' -e 'modifyColumnMandatory' -e 'addColumn(' \
-e 'DefaultValue' -e 'extends .*DBHelper' -e 'appendChangeColumn' -e 'appendChangeMandatory' -e 'appendDBType' \
--include=*.java .
grep -rn -e 'default-value' --include=*.xml src/main/webapp/WEB-INF/conf 2>/dev/null
3. Security / access checks
3.1 Group mapper of the ElementAccessManager removed (#30096) — mandatory if configured
- Remove the entry
<group-mapper .../>from the configuration of theAccessManager. - Every role rule of type
referencewhose path ends at aContacts:PersonContactgets an additional step to the person:<path> <step attribute="..."/> <step attribute="tl.accounts:Person#contact" inverse="true"/> </path> - Own
Mappingimplementations with more logic than "group or person to group" must be expressed as navigation steps in the affected role rules. If the mapping is not yet a reference in the model, model it there first. - The classes
com.top_logic.element.boundsec.manager.rule.GroupMapper,com.top_logic.tool.boundsec.manager.SimpleGroupMapperandcom.top_logic.contact.boundsec.manager.rule.ContactGroupMapperare removed; remove own code using or extending them.
Detect: grep -rn -e 'group-mapper' -e 'PersonContact' -e 'Mapping' -e 'GroupMapper' --include=*.xml --include=*.java .
3.2 Comments module and mandator-aware contacts removed (#30079) — deletes data
Comments: the migration tl-element:Ticket_30079_Remove_comments deletes the module tl.comments
and the table Comment. All existing comments are deleted. An application that declares
attributes of type tl.comments:Comment in its model must
- remove these attributes from its model (
*.model.xml) and from layouts, forms and the security configuration (grants on the moduletl.comments), - add an own migration that depends on
tl-element:Ticket_30079_Remove_commentsand deletes the attributes in itspre-processors, e.g.:<dependencies> <dependency name="Ticket_30079_Remove_comments" module="tl-element"/> ... </dependencies> <pre-processors> <!-- Reference: also deletes the stored links. --> <delete-reference name="my.module:MyType#comments"/> <!-- Derived property. --> <delete-property name="my.module:OtherType#comments"/> </pre-processors> - remove usages of the template
commons/comments/comments.xmland of classes fromcom.top_logic.element.comment.
Mandator-aware contacts: an application using classes from com.top_logic.contact.mandatoraware
or com.top_logic.contact.search (e.g. COSPersonContact / COSCompanyContact as implementation of
contact types, the SAP/Excel importers or ContactSearchComponent) must remove them or replace them by
own implementations. Usages of the filter SupportsContacts or the generator CompanyContact of
FilterFactory / GeneratorFactory must be removed. Likewise
com.top_logic.contact.filter.ContactListModelBuilder and
com.top_logic.contact.external.importer.ExcelExternalContactImporter are removed; remove their usages
in layouts, configuration and code.
Detect:
grep -rn -e 'tl.comments' -e 'commons/comments' -e 'com.top_logic.element.comment' \
-e 'contact.mandatoraware' -e 'contact.search' -e 'COSPersonContact' -e 'COSCompanyContact' \
-e 'ContactSearchComponent' -e 'SupportsContacts' -e 'CompanyContact' \
-e 'ContactListModelBuilder' -e 'ExcelExternalContactImporter' .
3.3 Attribute WRITE grants checked on objects created in the same transaction (#30255)
An application script that runs with security (the default) and sets an attribute with an attribute-level WRITE grant on an object created in the same transaction — e.g. a "created by" field the user may not edit — is now refused for a user without that grant. Either grant the role the right on the attribute or run that script without security.
Workarounds for the former wrong refusal of the pre-commit guard (e.g. a SecurityContextFactory
answering canCreate with yes for a container created in the same turn) can be removed.
The classes com.top_logic.tl.ai.core.security.PreCommitGuard and ModelSecurityPreCommitGuard are
removed. Remove own code using or implementing them; the tools check the rights of a write themselves
(Java tools via ModelSecurity, TL-Script via the engine).
Detect: grep -rn -e '<grant' --include=*.xml . | grep -i attribute and check scripts that create
objects and set such attributes (creation commands, <execute-script>, AI tools);
grep -rn 'PreCommitGuard' --include=*.java --include=*.xml .
4. React views (*.view.xml)
4.1 Sidebar of the shell in sections (#30044) — mandatory for shell overlays
The shell structures its sidebar into <section id="application"/> (empty, filled by the application)
and <section id="system"> with the administration. An overlay of the shell
(WEB-INF/views/app.view.xml of an application) that still names config:reference="system-separator"
or uses <separator> or <header-item> can no longer be loaded (the application answers /view/
with HTTP 500). Adapt:
- Entries placed before the system section with
config:position="before" config:reference="system-separator"are written into the application section without these two attributes:<sidebar config:ref="sidebar"> <items> <section id="application"> <nav-item id="home">...</nav-item> </section> </items> </sidebar> - A
<separator/>between own entries: put the entries before and after it into one<section id="…">each. - A
<header-item id="x">with label: replace it by<section id="x">with the same label (optionallabel,icon) and move the entries it covered into the section. Entry ids stay unchanged.
Detect: grep -rn -e 'system-separator' -e '<separator' -e '<header-item' --include=*.view.xml .
4.2 Colour role of rows, cells and displays (#30256)
A script in row-class, cell-class or css-class (on ResourceDisplayByExpression) that yields one
of the classes tl-status-error, tl-status-warning, tl-status-success or tl-status-info moves to
the corresponding role property and yields the role name without prefix:
<table row-class="$row.get(`my:Part#amount`) > 100 ? 'tl-status-warning' : null"/>
becomes
<table row-role="$row.get(`my:Part#amount`) > 100 ? 'warning' : null"/>
Likewise cell-class → cell-role on the column and css-class → role on
ResourceDisplayByExpression. A script that yields further own classes besides the status class keeps
those in the class property. A display with a role appears as a pill, not as coloured text.
The TL-Script functions securityGrantsRowClass and securityGrantsCellClass are renamed to
securityGrantsRowRole and securityGrantsCellRole, yield a role name and belong into row-role
resp. cell-role.
Java: RowClassFunction is renamed to RowFunction (RowFunction.rowClass(...), RowFunction.rowRole(...));
the record ColumnSetup has the additional component cellRole after cellClass.
Detect: grep -rn -e 'tl-status-' -e 'securityGrantsRowClass' -e 'securityGrantsCellClass' -e 'RowClassFunction' -e 'ColumnSetup(' .
4.3 Showing a unit only for one type: <visibility> instead of <access-control type> (#30258)
An <access-control> whose type only serves to hide a unit for objects of other types is replaced by
<visibility inputs="..." expr="$x.instanceOf(`<Type>`)"/>
(or complemented by it, if an access check is wanted too; its type is then a type all checked
objects have).
Detect: grep -rn '<access-control' --include=*.view.xml . | grep 'type='
4.4 <job-status> closable by default (#30250)
<job-status> offers a close button for an ended job by default, which clears the input channel.
Where the display of an ended job must stay, set closable="false".
A cancelled job whose body returns a value regularly after the cancellation delivers this value as
jobResult(...).
Detect: grep -rn -e '<job-status' -e 'jobResult' --include=*.view.xml .
4.5 Self-service views renamed (#30183)
React (tl-layout-view-selfservice, affects preview versions with #30041): the view
invitation-code.view.xml is now verification-code.view.xml, the action <check-invitation-code/>
is <check-verification-code/>, <accept-invitation/> is <create-own-account/>. An application that
overrides these views or uses the actions in own views renames them.
Detect: grep -rn -e 'invitation-code' -e 'check-invitation-code' -e 'accept-invitation' .
4.6 Agents and tools at /agent-api (#30200)
The key commands are gone. Objects are addressed with the IDs from the observation
(key / nodeId / rowId / value, see the respective argument schema); the table takes rowId
instead of rowIndex, drop expects keys as JSON array. Argument schemas are under schemas and
referenced via $ref; there is no params field any more. The compact view is called mode=actions,
pretty=true gives indented output. Adapt own clients, agent prompts and recorded scripts.
Detect: grep -rn -e 'agent-api' -e 'rowIndex' -e 'ByKey' -e '"params"' .
4.7 Log view of the administration moved (#30059)
The view admin/system/logs.view.xml (module tl-layout-view) is now admin/monitoring/logs.view.xml.
A same-path overlay of the application (WEB-INF/views/admin/system/logs.view.xml) or a
<view-ref view="admin/system/logs.view.xml"/> must use the new path; an overlay of the old path has no
base any more.
Detect: grep -rn 'admin/system/logs' . ; find . -path '*WEB-INF/views/admin/system/logs.view.xml' -not -path '*/target/*'
5. Java APIs and TL-Script
5.1 View commands without HandlerResult (#30151)
Own ViewCommand implementations, CommandModels, ButtonActions or @ReactCommandHandler methods:
- return type
voidinstead ofHandlerResult;return HandlerResult.DEFAULT_RESULT;is dropped; return HandlerResult.error(key)becomesthrow new TopLogicException(key);return HandlerResult.notExecutable(...)resp. checking anExecutableStatebecomesthrow CommandErrors.notExecutable(state);- a caller evaluating
command.execute(...)catchesTopLogicExceptioninstead of checkingisSuccess(); - a handler answering its caller declares the answer as msgbuf message and returns it instead of
ReactCommandReply.create().put(...).toResult(). Client code reads the answer fields as before; a failed command now answers withsuccess: falseanderror.
Detect: grep -rn -e 'ViewCommand' -e 'CommandModel' -e 'ButtonAction' -e 'ReactCommandHandler' -e 'ReactCommandReply' --include=*.java . | grep -v '^.*import'
5.2 Command lists and ViewCommandModel (#30063, #30207)
- Code reading or setting the command list of a configuration of
CommandCarrierElementorFormElement(getCommands()) getsList<ViewCommand.Config>instead ofList<PolymorphicConfiguration<? extends ViewCommand>>..view.xmlfiles are unchanged. ViewCommandModel.execute(ReactContext, Object)is nowexecute(Object); the command runs in the context the model was created with.- The public constructors of
ViewCommandModelandViewUploadCommandModelexpect theViewContextas first argument.ViewCommandModel.create(...)/forCommand(...)no longer accept anullcontext. RowCommandColumn.RowCommand.of(...)expects no context any more.- An own wrapper around a
ViewCommandModelthat only sets another execution context is superfluous.
Detect: grep -rn -e 'CommandCarrierElement' -e 'FormElement' -e 'ViewCommandModel' -e 'ViewUploadCommandModel' -e 'RowCommand.of' --include=*.java .
5.3 Own React controls: stable object IDs for scripting (#30200)
- Subclasses of
ReactControloverridingscriptingScalarState()overridescriptingScalarState(ProjectionMode mode)instead (the variant without parameter isfinal). Implementations ofScriptingNode.scriptingState()becomescriptingState(ProjectionMode mode). An object is entered into the projection withScriptingModelKey.putIdentity(projection, mode, objectIdentities(), ...)instead of by its key. - A control whose commands address objects holds their IDs in an
ObjectIdIndex, returns it fromobjectIdentities()and marks the ID arguments of its typed argument interfaces with@DisplayedObjectId(lists additionally with@JsonBinding(SimpleJsonListBinding.class)). ArecordCommandoverride that only translates IDs into keys is dropped. - Removed types:
ModelKeyArguments(constantKEYis now inScriptingModelKey),SelectByKeysArguments,SelectByNameArguments,DropObjectsArgumentsand the*ByKeyArgumentsof table and tree.
Detect: grep -rn -e 'scriptingScalarState' -e 'scriptingState(' -e 'ModelKeyArguments' -e 'SelectByKeysArguments' -e 'SelectByNameArguments' -e 'DropObjectsArguments' -e 'ByKeyArguments' -e 'recordCommand' --include=*.java .
5.4 Model value listeners (#30132)
An own class implementing com.top_logic.layout.form.ValueListener that is configured in a model
annotation <value-listeners> must implement com.top_logic.model.annotate.AttributeValueListener
instead: valueChanged(TLFormObjectBase object, TLStructuredTypePart attribute, Object oldValue, Object newValue)
receives the edited object (form overlay) and the attribute instead of the FormField. Model files and
<script> listeners are unchanged.
Detect: grep -rn -e 'implements .*ValueListener' -e 'value-listeners' .
5.5 DynamicVisibility moved (#30205)
DynamicVisibility is now in package com.top_logic.model.annotate (module tl-core) instead of
com.top_logic.layout.view.form. Adapt imports:
import com.top_logic.model.annotate.DynamicVisibility;
Detect: grep -rn 'layout.view.form.DynamicVisibility' --include=*.java .
5.6 Scheduler API and classic scheduler layouts (#30057)
Scheduler.enable(Task)/disable(Task)/isTaskEnabled(Task)/isEnabled(Task)andblockTask(Task)/unblockTask(Task)/isTaskBlocked(Task)are replaced byactivate(Task)/deactivate(Task)/isActive(Task)/resetActive(Task).Scheduler.isBlockingAllowed(Task)andTask.isBlockingAllowed()are removed.- Classic scheduler view: the command handlers
blockTask/unblockTaskare replaced bydeactivateTask/activateTask(plusresetTaskActive); the classes moved fromcom.top_logic.util.sched.layout.blocktocom.top_logic.util.sched.layout.active. The table columnsisBlocked,isEnabledandisBlockingAllowedare replaced byactive. Application layouts referencing them must be adapted. - A customer component library replacing the React part
FieldValuereceives the optional propspreformattedandaction; the part list contains the partCopyButton. - Further removed API: the filters
com.top_logic.util.sched.entry.filter.EnabledSchedulerEntryFilterandManuallyStartedSchedulerEntryFilter, the classesBlockStateandBlockStateRenderer(com.top_logic.util.sched.layout.block),TaskImpl.isBlockedByDefault(), and onSchedulerEntrythe constructorSchedulerEntry(Task, boolean)and the methodssetEnabled/isEnabled/setAttached/isAttached/createThread. Own layouts and code using them askScheduler.isActive(Task)instead or drop the usage.
Detect: grep -rn -e 'blockTask' -e 'unblockTask' -e 'isTaskBlocked' -e 'isTaskEnabled' -e 'isBlockingAllowed' -e 'sched.layout.block' -e 'isBlocked' -e 'FieldValue' -e 'SchedulerEntryFilter' -e 'BlockState' -e 'SchedulerEntry' .
5.7 ModificationListener and Committable (#30218, #30225)
ModificationListener.createModification(KnowledgeBase, CommitActions, Map created, Map updated, Map removed)becomescreateModification(KnowledgeBase, CommitActions, PendingChanges changes); read the maps viachanges.getCreated(),changes.getUpdated(),changes.getRemoved().ModificationListener.notifyUpcomingDeletion(KnowledgeBase, CommitActions, KnowledgeItem)gets theCommitActionshandle as additional second parameter (ascreateModificationalready does).Committable.complete(CommitContext)is removed. Move such code to anAfterCommitAction(registered on theTransaction, or in aModificationListeneron theCommitActionshandle; the action receives theUpdateEventof the commit) and drop the@Overridemethod.- An implementation of
StorageImplementationthat implements bothColumnStorageandSeparateTableStoragemust overrideaddChangeLocators(...)andcanDetectValueChanges()to combine both behaviours (the compiler reports the conflicting default methods).
Detect: grep -rn -e 'ModificationListener' -e 'createModification' -e 'notifyUpcomingDeletion' -e 'complete(CommitContext' -e 'implements .*ColumnStorage' -e 'SeparateTableStorage' --include=*.java .
5.8 Background jobs and thread bindings (#30196, #30250)
ThreadLocalBinding,BindingRegistryandBindingSnapshotmoved fromcom.top_logic.tl.ai.core.bindingtocom.top_logic.basic.thread. Adapt imports.- Cancelling a
<start-job>job or stopping a scheduler task no longer interrupts the thread. Code relying on being woken up (ownThread.sleep,Object.wait, blocking I/O,Future.get) runs such waits withCancelScope.sleep(...)resp.CancelScope.runInterruptibly(...)— never with database accesses inside. - Own implementations of
JobMonitorimplementuncancelable()(a delegating implementation passes it to the wrapped monitor). - The record
JobStatehas the additional componentcancelable(beforecontrol); code constructingJobState(e.g. tests) passes it. ViewActionChain.run(..., Consumer onComplete, Runnable onSettled)is gone; instead of theonSettledcallbackViewActionChain.run(context, actions, arguments, after, onComplete, holds)takes aViewActionChain.HoldObserver.
Detect: grep -rn -e 'tl.ai.core.binding' -e 'Thread.sleep' -e '\.wait(' -e 'JobMonitor' -e 'JobState' -e 'ViewActionChain.run' --include=*.java .
5.9 Login methods (#30067)
An own implementation of com.top_logic.base.accesscontrol.loginmethod.LoginMethod replaces
getInitiationUrl(String returnToUrl) by getInitiationUrl(String returnToUrl, String windowName) and
adds the window name (if not null) as parameter ExternalAuthenticationServlet.WINDOW_PARAM to the
initiation URL, see Pac4jLoginMethod.
Detect: grep -rn -e 'LoginMethod' -e 'getInitiationUrl' --include=*.java .
5.10 Chat API (#30126)
Only for applications that address the chat API themselves or use tl-chat classes:
- The JSON API
/chat/*(ChatServlet) is gone; an own client calls the commands of the chat controls (name constants inChatControl), e.g. via the headless access/agent-api. ChatSessionRegistry:windowId(session, subSession),resolveSubSession(session, windowId)andresolveTracker(session, windowId, conversation)are gone. The tracker of a window istracker(subSession, conversation, account); the window name is on the sub-session (subSession.getLayoutContext().getWindowId().getWindowName()).TaskProgressTracker:get/setUserName,get/setWelcomeNameandget/setWindowIdare gone;getAccount()returns the tracker's account.ChatViewBinding:writeSharedState,subjectOf,windowId,contextPath,userName,welcomeName,languageandauthenticatedare gone (the state is written on every render bywriteRenderState).presentableandpresentableNamesadditionally expect the account of the run.useTLCommand()anduseTLUpload()(TypeScript) now return the command's answer (Promise<CommandResponse | undefined>instead ofPromise<void>); callers ignoring the result are unchanged.
Detect: grep -rn -e '/chat/' -e 'ChatServlet' -e 'ChatSessionRegistry' -e 'TaskProgressTracker' -e 'ChatViewBinding' -e 'useTLCommand' -e 'useTLUpload' .
5.11 AI tools: commit messages (#30276)
- Own
Toolimplementations callingTransactionScope.begin(ResKey): the method is gone; set the message on commit withTransactionScope.commit(ResKey). - An own writing tool may override
commitMessage(arguments, context)(or set@ToolMethod(commitMessage = ...)) to describe its changes in the history; otherwise "Werkzeugaufruf <Name>" (tool call) appears. - Callers of
ScriptToolStore.create/updateadditionally pass the commit message expression (nullfor none resp. unchanged).
Detect: grep -rn -e 'TransactionScope.begin' -e 'ScriptToolStore' -e '@ToolMethod' --include=*.java .
5.12 Self-service Java API (#30041, #30183, #30223)
- Code constructing
CreateAccountDialogpasses no continuation command and passes anAccountRequest(e.g.new InvitationRequest(invitation)) instead of anInvitation. CheckInvitationCodeis renamed toCheckAccountCode.LoginViewDialog.loginUserAndReload(...)now returns aHandlerResult: a caller passes an unsuccessful result (lock, maintenance mode) on to its caller instead of ignoring it.- Code calling
sendInvitationfor an invitation that is not open gets an error; an expired invitation is sent again withinvitationResend. - Code calling
InvitationModule.getInvitationMail(),getVerificationMail(),getResetPasswordMail()orgetResetMFAMail()uses the operationssendInvitation,sendVerificationMail,sendResetPasswordMailorsendResetMFAMailinstead.
Detect: grep -rn -e 'CreateAccountDialog' -e 'CheckInvitationCode' -e 'loginUserAndReload' -e 'sendInvitation' -e 'InvitationModule' --include=*.java .
5.13 React configuration editor: ConfigAnnotationControlProvider (#30266)
Only for applications with an own ConfigAnnotationControlProvider: a provider no longer refuses a
property by returning null from createControl(...) but via accepts(ConfigFieldModel model, A annotation).
createControl is only called for accepted properties and must not return null. Move the former
null condition into an override of accepts (e.g. return ConfigControlService.holdsText(model);).
If the field should take the whole line (multi-line content), also override
rendersWholeLine(ConfigFieldModel, A) with true.
Detect: grep -rn 'ConfigAnnotationControlProvider' --include=*.java .
5.14 TL-Script: try() passes a ResKey to catch (#30265)
The catch function of try() always receives the error message as ResKey, no longer as string. No
change is needed for display and log(). A script that processes the message as string (length,
regular expression, comparison with a string, concatenation) converts it with localize() first:
try(..., catch: msg -> $msg.localize().contains("..."))
Detect: grep -rn 'catch:' --include=*.xml --include=*.script --include=*.tls . (and scripts stored in the database, e.g. via the model editor)
5.15 Scripted tests: LastRevisionNamingScheme (#29847)
A script that assigns LastRevisionNamingScheme after a commit of another user than the one executing
the assignment must execute the assignment as the committing user (user-id of the SetGlobalVariableOp).
Detect: grep -rln 'LastRevisionNamingScheme' --include=*.script.xml .
5.16 Sorting helpers of React table columns (#30204)
ColumnProviderService.labelOrder(), ColumnProviderService.unreadableLast(Comparator) and
ScriptedFilter.order(Comparator) are removed. Own Java code building the sort of a React table column
with them uses ColumnProviderService.cellSort(...) instead.
Detect: grep -rn -e 'labelOrder' -e 'unreadableLast' -e 'ScriptedFilter' --include=*.java .
5.17 OpenApiServlet has a new super class (#30177)
OpenApiServlet now extends NoContextServlet instead of AbstractTopLogicServlet; the method
serviceWithLogMark(HttpServletRequest, HttpServletResponse) is removed. An own subclass of
OpenApiServlet overrides doService(HttpServletRequest, HttpServletResponse) instead.
Detect: grep -rn -e 'extends OpenApiServlet' -e 'serviceWithLogMark' --include=*.java .
6. Stylesheets and themes
6.1 Theme role surface-interactive-selected renamed (#30000)
--tl-surface-interactive-selected → --tl-surface-interactive-strong (neutral grey, no longer a
selection). There is no alias. Stylesheets and MUI themes that drew a selection with the old name read
the new selection roles instead:
| Old | New |
|---|---|
--tl-surface-interactive-selected (neutral strong fill: track, skeleton, pill, avatar, drop target, field button hover) |
--tl-surface-interactive-strong |
--tl-surface-interactive-selected (selected row, node, item) |
--tl-surface-selected, hover --tl-surface-selected-hover, edge --tl-border-selected |
New roles: surface-selected, surface-selected-hover, border-selected. Customer themes get the
selection roles computed by derive; MUI themes feed MUI's selected state into surface-selected.
The complete table is in the design system repository, docs/migration/altlast.md.
Detect: grep -rn 'surface-interactive-selected' --include=*.css --include=*.ts --include=*.tsx --include=*.js --include=*.json --include=*.xml .
7. Further notes (no action required)
- Duplicate links (#30031): the automatic migration logs every link it removed or shortened; read the migration log after the first start.
- Agent screenshots (#30054): screenshots from before this release belong to no chat; the data migration deletes them.
- Mandatory columns in migrations (#30167): adding a mandatory column to a large table now takes
longer (measured on PostgreSQL 15: about 12 s per million rows) and needs about twice the table's
space until the next
VACUUM. Database defaults already present stay untouched. In the UI,default-valueofAddDatabaseColumnProcessoris labelled "Fill value"; the configuration name is unchanged. - Dynamic visibility (#30205): an attribute whose dynamic visibility yields
mandatorynow prevents saving a React form or row-edit table with an empty value; withread-only,disabled,hiddenoreditablean attribute declared mandatory in the model is no longer checked as mandatory. - Security parents of structured elements (#30079):
StructuredElementWrapperno longer overridesgetSecurityParents(), so configured security parents now also apply to structured elements;Documentreturns its containingWebFolderin addition to the configured security parents; a deleted object has no security parents. Users may inherit roles they did not have before — check the access rights of structured types with a configured security parent. - Claude 5.5 workarounds (#30251): workarounds in
<model-overrides>(e.g.reasoning="false"forclaude-haiku-5-5) can be removed. - Self-service mails (#30223): a self-service mail that cannot be sent is reported as warning in the application log; the default scripts no longer show the English failure text to the user.
- Tables (#30227): new theme role
surface-layer-mutedand typography classoverline; separators in dark mode are slightly stronger, the table body has no column lines. - Commit and rollback (#30259): after a successful database commit,
Committable.rollback()is no longer called; an exception of a synchronousUpdateListeneris logged and no longer thrown out ofcommit(). Flags in own committables that guarded against this are no longer needed. - Access check for another person (#30269):
ModelAccessRights.isAllowed(Person, ...)decides by the given person, not by the bypass or admin state of the current context. A workaround switching the identity before the check is no longer needed; code that relied on the old bypass now getsfalse. - Hidden instead of disabled (#30025): React commands and units for which the user holds the needed role on no object are hidden instead of disabled.
- Executability rules first (#30211): configured
<executability>rules of a React command now apply before<access>and the built-in rules (e.g. of<delete-object>);clique="delete"makes a command destructive (red confirmation in<confirm>). - Busy indicator (#30237): a command unanswered for longer than
busy-delay(default 500 ms,ViewConfig) blocks the page with a busy indicator. Own React controls sending requests next to the input (suggestions, lazy loading) should set the optionbackground(BACKGROUND_COMMAND), otherwise the page blocks during these requests. - Slots (#30045): a
<slot-content>that reaches no<slot>is logged as warning (diagnosisunrouted); setoptional="true"for content that has a slot only in some shells. Panes of split panels and sidebar entries have slot positions of their own, which can change the routing. /agent-apiwithout login (#30125): a request without valid credential gets401with JSON instead of a redirect to the login page.- Session ID on login (#30069):
SessionService.loginUserchanges the session ID on every login with an existing HTTP session (protection against session fixation), also for an external login. - Generic OpenID login (#30179): the button of the generic OpenID client shows
tl_pac_label(by default a generic "single sign-on" label instead of the technical client name). An existinguseSSOapplication may addpac4j-generic.xmlto itsmetaConf.txtto have the login on the development server too (optional).
8. Verification checklist
mvn clean installof the application (with tests if possible).- Start the application against a copy of the production database first: the migrations of #30031 (unique link tables), #30079 (comments deleted) and #30167 (column handling) run at startup.
- Read the startup and migration log: no configuration errors (
blocked-by-default,group-mapper,user-mappingattributes, tool access policy with unknown models), no errors ofTicket_30031_unique_link_tables. - Log in, open
/view/(shell overlay, #30044) and every React view; open the classic layouts with scheduler, self-service and search. - Check styling of selected rows, nodes and items in own stylesheets / MUI themes (#30000) and row, cell and display colours (#30256).
- Check access: users without admin rights see the expected units and commands; role rules over contacts (#30096) and security parents of structured types (#30079) give the expected rights.
- Report to the user every step that was skipped, needed a decision, or touches data (deleted comments, removed duplicate links, changed access rights).