enhancement
major
minor
defect
critical
major
minor
major
#30151
React View Layer: Commands Without HandlerResult - View commands report errors as exceptions; React command handlers return their response as a msgbuf message
Initial Situation
A @ReactCommandHandler returned com.top_logic.tool.boundsec.HandlerResult (or void, which meant HandlerResult.DEFAULT_RESULT ), the result type of the command handlers in the classic interface. Since #30126, a handler can return fields to its caller (ReactCommandReply, e.g., the ID of a created object). The response was attached to the HandlerResult as a TypedAnnotatable property:
{{{#!java @ReactCommandHandler(CMD_CREATE) HandlerResult handleCreate(Map<String, Object> arguments) { Item item = createItem(arguments); return ReactCommandReply.create().put(FIELD_ID, item.getId()).toResult(); } }}}
ReactServlet retrieved the response using ReactCommandReply.of(result) and wrote its fields, along with "success," to the JSON response. Uploads (UploadHandler) followed the same process. A command from the view layer (ViewCommand.execute()) and the command chain of the React controls (CommandModel, ButtonAction, CommandItem, UploadCommandModel, activation and selection callbacks) also returned a HandlerResult.
Issues
- The return type of a React command was a type from the classic UI, which includes many features that are irrelevant here: lists of processing errors, continuations, and ` DefaultHandlerResult`.
- The fact that a command returns something was not specified in its signature. It was an invisible annotation that only ReactCommandReply.of() could detect.
- The shared `HandlerResult.DEFAULT_RESULT ` must not be annotated. Consequently, ` ReactCommandReply.attachTo()` silently created a new result, and the caller had to use the returned object instead of the one passed in.
- Errors and responses were handled through two separate channels: HandlerResult.isSuccess() and error keys for the window (CommandErrors), which served as annotations for the caller. A failed command responded to the client with `success: true`.
- A command in the view layer can only succeed or fail; a result has no meaning there. Errors were handled via HandlerResult.error()/`notExecutable()` instead of exceptions.
Solution
HandlerResult has been removed from the React UI command chain; it remains only in classic components (in tl-ai-core, for example, the commands of the tool access policy).
View Layer and React Controls Command Chain
- ViewCommand.execute(...), CommandModel.executeCommand/perform, UploadCommandModel, ButtonAction, CommandItem.Command, and the activation handlers for tables, trees, and links are void; menu selections and field commits are Consumer.
- A rejection is handled by `throw CommandErrors.notExecutable(state) ` (a ` TopLogicException ` with severity "Warning" and the reason as the detail); a business error is handled by `throw new TopLogicException(key)`; and an internal error is handled by any ` RuntimeException`.
- CommandErrors displays an exception in the window: title and details from the I18NFailure objects in the cause chain, severity from `I18NRuntimeException.getSeverity(); another exception with the general title “Command failed,” its message as the detail, and a log entry.
- Callers that evaluated the result of a command (ValueInputElement, ConfigFormStateHandler, ChatCommandTool, StartJobAction, VerifyIdentityAction, ViewUploadCommandModel) catch the exception.
React Command Handlers
- A @ReactCommandHandler and an UploadHandler return void or their response as an msgbuf message (DataObject). ReactCommandMap only allows these return types; ReactCommandAnswer returns a response message with a success, error, or errorCode property.
- Errors are exceptions. ReactServlet displays them in the window and returns {"success":false,"error":...}; upon success , it returns {"success":true, ...fields of the response}. A request that the server cannot process at all (unknown control, obsolete interface) retains its error status with ` errorCode ` (`RouteRefusal ` in `answer.proto`).
- ScriptingSession.act/`replay` returns the response and throws the error; ReactWindowReplay, StepReplayAction, and AgentServlet follow the same contract.
- The REPLY annotation and ReactCommandReply have been removed. The chat responses (ChatControl) are messages in ` messages.proto ` (`StreamAttachReply`, `SessionOpenReply`, ` SessionDeleteReply`, ` ChatSendReply`, `ChatStatusReply`, `SubagentMessageReply`, `CancelReply`, ` AttachmentUploadReply`).
- The developer articles view-layer/client-server-protocol, view-layer/commands, view-layer/headless-interface, and view-layer/agent-access describe the contract.
Migration
Applications with their own `ViewCommand` implementations, `CommandModel`s, `ButtonAction`s, or `@ReactCommandHandler` methods should make the following changes:
- Return type ` void ` instead of ` HandlerResult`; `return HandlerResult.DEFAULT_RESULT` is no longer used.
- `return HandlerResult.error(key) ` becomes ` throw new TopLogicException(key)`, `return HandlerResult.notExecutable(...) ` or checking an ` ExecutableState ` becomes `throw CommandErrors.notExecutable(state)`.
- A caller that evaluates ` command.execute(...)` catches ` TopLogicException` instead of checking `isSuccess()`.
- A handler that responds to its caller declares the response as an msgbuf message and returns it, instead of providing ReactCommandReply.create().put(...).toResult(). Client code reads the response fields as before; a failed command now responds with ` success: false ` and ` error`.