enhancement
major
minor
defect
critical
major
minor
major
#30250
TL Views: <start-job> / <job-status> - close button for an ended job, point of no return, partial result of a cancelled job, closing the dialog from the follow-up, follow-up sees the job's commits
tl-layout-view 8.0.0-alpha12.819. Combines tickets #30193, #30194, #30195, #30249, and #30250 (Long-running jobs in TL Views: <start-job> and <job-status>).
Issues
The display of a completed job cannot be closed (#30250)
A failed or aborted <start-job> terminates its command; the error remains intentionally visible in the job’s last state. The standard page displays the view as long as the channel holds a job:
<visible-if expr="$importJob != null" input="importJob"> <inset><job-status input="importJob"/></inset> </visible-if> <table …/>
A successful job is cleared by its subsequent step (<write-channel name="importJob" clear="true"/> in the <with-result>). A failed or aborted job skips the subsequent step; its display (status, steps, progress bar, errors) remains permanently above the table; <job-status> offers no option to close it. Only the next job removes them. (E-Home: ETS Import, Project Update, Product Import.)
Canceling During the Final Commit (#30194)
A cancelable job that is imported in its own transaction and commits last continues to offer the option to cancel during the commit. Canceling at this point no longer stops the commit—the data has been saved—but JobRunner.run makes its decision based solely on the cancel flag: the job ends as canceled, its result is discarded, and the waiting command is canceled. The user sees “aborted,” even though the import exists.
An aborted job does not return a partial result (#30195)
An import of multiple files commits each file individually; an abort only rolls back the currently running file. A body that catches the AbortExecutionException and returns a report on the files already imported still ends as aborted with no result, and StartJobAction aborts the command. The user is not informed that two out of three files were imported.
`<close-dialog>` within the `<with-result>` of a `<start-job>` is ignored (#30193)
A <window> dialog starts a job and is supposed to close after it finishes (<with-result><close-dialog/><open-dialog …/>…</with-result>). Since #29631, a dialog with a suspended command cannot be closed; but the `<with-result> ` is still running as a continuation of that exact command, which therefore continues to count as suspended. The `<close-dialog> ` is deferred; the report dialog opens briefly and then closes again, while the job dialog remains open.
The `<with-result>` does not see the job’s commits (#30249)
Since #30216, the body runs in a sub-session derived from the window. The window only learns of the job’s commits through model events, which are synthesized the next time the window is entered. The `<with-result> ` runs before this: a `<write-channel>` that selects the object created by the job in a table cannot yet find the row, and the selection is lost—this is reproducible if the window has committed during the job itself.
Solution
Close button (#30250): <job-status> provides a close button (×) at the end of the header for a completed job (completed, failed, or aborted). Clicking it clears the input channel; the display disappears, as does any surrounding <visible-if expr="$importJob != null">. The button is not visible while the job is running. The closable attribute (default true) disables it. It is only available on a writable channel: ViewChannel must return isWritable() for this (a derived channel without an inverse function is not writable). A click that coincides with the start of a new job is ignored.
Point of no return (#30194): JobMonitor.uncancelable() (TL-Script: jobUncancelable($job)) indicates that the body will now complete its work: if the job had already been canceled, it ends immediately as canceled; otherwise, the Cancel button disappears, `cancel()` is rejected, and the job ends with whatever the body returns. Whether a cancellation is accepted is determined by `JobRunner ` under the same lock as the point of no return; `JobState ` stores the cancelability as a component of the snapshot.
Partial result of a canceled job (#30195): If a body returns a value normally after a cancellation, the canceled job retains this value as its result (JobState.result(), jobResult($s), in the display). <start-job> accepts the optional follow-up chain <on-cancel as="…">, which runs with this (possibly empty) result instead of canceling the command; afterward, the chain continues as after <with-result>. Without <on-cancel>, an abort terminates the command as before; a failed job always terminates.
Suspended commands (#30193): A command is considered suspended only as long as an action holds its chain. If the action returns the chain (resume or cancel), the suspension ends before the continuation runs; a <close-dialog> in the continuation closes its dialog immediately. If the continuation suspends again (another <start-job>, <confirm>), it is counted as suspended once more. A <close-dialog> from another command, while a job is running, continues to wait until the job ends and then closes its own dialog (not the currently topmost one). ViewActionChain reports the holding and return of the chain via ViewActionChain.HoldObserver.
Job commits before resumption (#30249): Entering a window from another thread (ReactWindowReplay.inWindow, including via the window name as used by ChannelPublisher for a job’s snapshots and <source-channel> ) synthesizes the window’s model events even before the action. Resuming a terminated job thus sees the job’s objects as if after a synchronous command.
Documentation and demo: commands.md (section on <start-job> / <job-status>), contract job-status.contract.md; the “Long-running job” demo from tl-demo-react demonstrates closing, the point of no return, partial results upon cancellation with <on-cancel>, and a job dialog that closes automatically after the job is complete.
Migration
- By default,<job-status> provides a Close button for a completed job, which clears the input channel. To keep the display of a completed job visible, set closable="false".
- Custom implementations of JobMonitor must implement the uncancelable() method (a delegating implementation passes it on to the wrapped monitor).
- The `JobState ` record has the additional `cancelable` component (before `control`); code that constructs `JobState` itself (e.g., in tests) must pass this value.
- ViewActionChain.run(..., Consumer onComplete, Runnable onSettled) is deprecated; instead of the onSettled callback, ViewActionChain.run(context, actions, arguments, after, onComplete, holds) takes a ViewActionChain.HoldObserver.
- A canceled job whose body returns a value normally after cancellation returns this value as jobResult(...).