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

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

## How to use this document

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

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

---

## 1. Build and platform

### 1.1 Version bump
Set the TopLogic version to `8.0.0-alpha12` (parent `tl-parent-app` / `tl-parent-core` version, or
the `tl.version` property).

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

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

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

Additional changes for applications coming from tl-ai 2.1.x (from a Trac comment on #29924, not in
the release notes):
- `tl-license-professional` is no longer a dependency of `tl-ai-core`. If the application got it
  transitively, declare it explicitly.
- Secured TL-Script (also in AI tools) returns only objects the user may discover (#29782): `all()`,
  references and inputs are filtered.
- The prompt expression of `AgentTask` (tl-chat) is evaluated without security checks, like the engine's `ScriptTask`.

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

### 1.4 Polymorphic configuration getters use wildcards (#30037) — Java
Engine getters/setters of properties holding a polymorphic configuration now declare
`PolymorphicConfiguration<? extends X>` (also inside `List<…>`, `Map<…>`) instead of
`PolymorphicConfiguration<X>`.
- Variables receiving such a getter's result: declare them as `PolymorphicConfiguration<? extends X>`.
- A sub-interface redeclaring such a getter should use the wildcard type too.
- Instantiating in a stream may need a type witness: `configs.stream().<X> map(context::getInstance)`.
- The application's own configuration interfaces must follow the same rule: a conformance check runs
  in the application's `TestAll` and reports violations (temporary opt-out:
  `test-polymorphic-configuration-wildcards="false"` in the global test configuration).
- XML configurations and data are unchanged.

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

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

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

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

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

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

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

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

---

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

### 2.1 Setup services became startup hooks (#29944) — mandatory if referenced
These modules no longer exist; remove any `<module key="…$Module" …/>` entry naming them (ModuleSystem configuration):
`InitialDataSetupService$Module`, `AccessConfigurationSetupService$Module`,
`InitialProcessSetupService$Module`, `ImportDocumentationModule$Module`, `AgentNameCheck$Module`,
`SecDemoSetupService$Module`.

| Old service | New action class (hook) | Hook id |
|---|---|---|
| `com.top_logic.knowledge.service.db2.InitialDataSetupService` | `com.top_logic.knowledge.service.db2.InitialDataSetup` | `initial-data` |
| `com.top_logic.tool.boundsec.config.AccessConfigurationSetupService` | `com.top_logic.tool.boundsec.config.AccessConfigurationSetup` | `access-configuration` |
| `com.top_logic.bpe.execution.engine.InitialProcessSetupService` | `com.top_logic.bpe.execution.engine.InitialProcessSetup` | `initial-workflows` |
| `com.top_logic.doc.ImportDocumentationModule` | `com.top_logic.doc.ImportDocumentation` | `documentation` |
| `com.top_logic.tl.ai.core.security.AgentNameCheck` (service) | same class as action | `agent-name-check` |
| `com.top_logic.ai.demo.secdemo.SecDemoSetupService` | `com.top_logic.ai.demo.secdemo.SecDemoSetup` | `secdemo` |

- A service that was switched off (`value="false"`) → remove the hook instead:
  ```xml
  <config service-class="com.top_logic.util.StartupHooks">
    <instance>
      <hooks>
        <hook id="initial-workflows" config:operation="remove"/>
      </hooks>
    </instance>
  </config>
  ```
- Overriding the security demo config (`<config service-class="com.top_logic.ai.demo.secdemo.SecDemoSetupService">`)
  → configure the action of hook `secdemo`:
  ```xml
  <config service-class="com.top_logic.util.StartupHooks">
    <instance>
      <hooks>
        <hook id="secdemo">
          <action class="com.top_logic.ai.demo.secdemo.SecDemoSetup" initial-password="..."/>
        </hook>
      </hooks>
    </instance>
  </config>
  ```
- An own service that waited (`@ServiceDependencies`) for one of these setup services to see the
  imported data must itself become a startup hook (a `StartupAction`), positioned after the
  corresponding hook (`config:position="after" config:reference="<id>"`). Hook option
  `on-error="fail|log-error|log-warn"` (default `fail`).
- Java code using static helpers/constants moves to the new class names (e.g.
  `InitialProcessSetupService.importWorkflow()` → `InitialProcessSetup.importWorkflow()`,
  `InitialDataSetupService.FILE_SUFFIX` → `InitialDataSetup.FILE_SUFFIX`).
- Already imported data is not re-imported (markers in `DBProperties` are unchanged).

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

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

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

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

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

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

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

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

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

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

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

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

---

## 3. Security / access checks

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

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

### 3.2 Thread context / security bypass API (#29798) — Java
Removed API and replacements:
| Removed | Use instead |
|---|---|
| `ThreadContextManager.inInteraction(String, …)`, `ThreadContextManager.inSystemInteraction(Class, …)` | `ThreadContext.inSystemContext(Class, …)` |
| `TLContextManager.inRootPersonContext(…)` | `ThreadContext.inSystemContext(Class, …)` or `TLContextManager.inPersonContext(PersonManager.getManager().getRoot(), …)` |
| `ThreadContext.pushSuperUser()` / `popSuperUser()` / `resetSuperUser()`, `ModelAccessRights.uncheckedSecurity(…)` | `ThreadContext.withoutSecurityChecks(…)` (keeps the person) or `ThreadContext.inSystemContext(…)` (no person) |
| `TLContext.setCurrentPerson(…)`, switching users via `setPerson(…)` / `setContextId(…)` | `TLContextManager.inPersonContext(person, …)` |
| protected hook `ThreadContext#isAdminContext()` | rename override to `isUserAdministrator()`; ask `ThreadContext.isAdmin()` (also instead of `TLContext.isAdmin()`) |

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

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

---

## 4. React views (`*.view.xml`)

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

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

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

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

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

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

<table inputs="filterState as state" row-as="ticket"
       row-class="if($ticket.get(demo.tickets:Ticket#done) &amp;&amp; $state == 'all', 'tl-status-info', null)">
```
Note: lambdas *inside* an expression (e.g. `recursion(p -> …)`, `map(m -> …)`) stay; only the leading
parameter head of the whole script is removed.

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

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

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

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

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

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

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

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

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

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

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

---

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

| Old | New |
|---|---|
| `@ControlProvider(MultiLineText.class)` | `@MultiLine` (optional `rows = …`) |
| `@ControlProvider(CommonMarkText.class)` | `@CommonMark` |
| `@ControlProvider(CodeEditorControl.CPHtml.class)` / `CPXml` / `CPText` | `@Code(language = CodeLanguage.HTML)` / `XML` / `PLAIN` |
| `@ControlProvider(ConfigXMLEditor.CP.class)` (with `@PropertyEditor(ConfigXMLEditor.class)`) | `@Code(language = CodeLanguage.XML)` (keep the property editor) |
| `@EditorControlConfig(language = "ace/mode/json")` + `@PropertyEditor(DefaultCodeEditor.class)` | `@Code(language = CodeLanguage.JSON)` (both removed) |
| `@ControlProvider(StructuredTextControlProvider.class)` | `@Html` |
| `@ControlProvider(MacroControlProvider.class)` | `@Html(features = StructuredTextConfigService.FEATURE_SET_MACRO)` |
| `@ControlProvider(TLScriptCodeEditorControl.PlainCP.class)` / `CP.class` on an `Expr` property | remove (value type `Expr` suffices) |
| `@ControlProvider(GroupInlineControlProvider.class)` | `@Inline` |
| `@ControlProvider(SelectionControlProvider.class)` on a selection | remove (selection dialog from 20 options or for tree options) |
| `@ControlProvider(PasswordInputControlProvider.class)` | `@Encrypted` |
| `@ConfigControl(X.class)` | own domain annotation marked `@ControlAnnotation` + entry in `<annotation-controls>` of `ConfigControlService` (SPI `ConfigAnnotationControlProvider`) |
| application-specific ControlProvider via `@ControlProvider` | own domain annotation with `@ControlAnnotation`; entry in `<annotation-controls>` of the `EditorFactory` (SPI `AnnotationControlProvider`) and, if also edited in React UI, in `<annotation-controls>` of `ConfigControlService` |

- XML customizations: `<annotation config:interface="com.top_logic.layout.form.values.edit.annotation.ControlProvider" value="…"/>`
  or `<control-provider value="…"/>` in `<customizations>` → tags `<multi-line rows="…"/>`,
  `<common-mark/>`, `<code language="json"/>`, `<html features="…"/>`, `<inline/>`.
- Own `<control-providers>` of `ConfigControlService` → `<annotation-controls>`. An `<annotation-controls>`
  entry for an own annotation requires `@ControlAnnotation` on it (else config loading fails).
- `MultiLineText` configuration with `rows` is removed → `@MultiLine(rows = …)`.
- Direct references to `CodeEditorControl.CPXml/CPHtml/CPText`, `ConfigXMLEditor.CP`,
  `MacroControlProvider` (wysiwyg), `EditorControlConfig`, `DefaultCodeEditor` → `@Code`/`@Html` or
  `CodeEditorControl.CP` / `StructuredTextControlProvider`.
- React WYSIWYG attributes (`tl.model.wysiwyg:Html`) are checked with SafeHTML on save; stored content
  with markup outside the whitelist (e.g. `<section>`, custom elements) can only be saved again after
  removing that markup (data issue — inform the user, do not change data automatically).

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

---

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

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

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

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

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

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

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

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

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

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

### 6.4 Foundation pieces and editors (#29914)
States: picking `body[data-tl-state~="picking"]`, anchor flash `[data-tl-anchor][data-tl-state~="flash"]`,
dragging `[data-tl-state~="dragging"]`, touch-drag armed `[data-tl-armed]`, interactive tooltip
`[data-tl-state~="interactive"]`, editable compact field `[data-tl-state~="edit"]`, editor error `[aria-invalid="true"]`.

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

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

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

---

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

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

---

## 8. Verification checklist

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