major
#29814
TL Views: no supported way for a server-side background source (a bus listener) to push a channel value into a view; only <start-job> does it through internal API
tl-layout-view / tl-layout-react 8.0.0-alpha10.
Problem
A view whose data comes from a server-side event source (here: a KNX group monitor receiving bus telegrams on a Calimero listener thread; equally a message queue, a serial port, a websocket) has no declared way to update its channels from that thread. The views follow commits (observed-types) and commands; a channel can only be written from within an interaction. <start-job> (JobRunner) is the one element that updates a view from another thread, and it does so with the internal ReactWindowReplay.inWindow(...) plus ThreadContextManager.inContext(...).
The E-Home monitor therefore calls that internal API from Java (WindowPublisher): the listener thread enters the user's session context, replays into the window and writes the snapshot into a channel; throttled to one update per 250 ms. It works (the table updates over the window's event stream), but it rests on internals that may change, and nothing documents the contract (lifecycle when the page closes, the session ends, or the window is rebuilt).
Expected
A public element or API for a view-owned live source: e.g. <channel name="…" source="…"> with a ChannelSource Java interface whose publish(value) may be called from any thread, with the layer handling context, throttling, replay into every window showing the view, and the lifecycle (unsubscribe when the view is destroyed; a close callback for the source). Documented in the view-layer chapter.
Workaround in E-Home
de.haumacher.ehome.monitor.WindowPublisher (uses ReactWindowReplay.inWindow); the monitor disconnects when the publisher notices the page is gone.
Lösung
ChannelPublisher (com.top_logic.layout.view.channel, public Java API, implements ChannelSink): delivers values into a channel of a window from any thread. publish(value) never blocks the caller and never runs channel listeners on the calling thread: it keeps the latest value and hands the delivery to a SchedulerService thread - at once when the update interval since the last delivery has passed, otherwise at the end of the interval (intermediate values are conflated: the channel holds state, not events; with interval 0 every value is delivered in order). publishNow(value) delivers without waiting for the interval, still ordered after what is being delivered. The delivery runs in the sub-session captured when the publisher was created and under the window's interaction (ReactWindowReplay.inWindow), so channel listeners and the updates reaching the browser are serialized like a command. A window whose page is gone drops the value (a constructor flag keeps applying it, which JobRunner uses so that whoever waits for a job is told); without a window registry (headless, tests) the channel is written directly. A failure while applying is logged and does not stop later deliveries. close() ends the delivery. JobRunner (<start-job>) publishes its snapshots through this class; its private throttle and delivery code is gone. Snapshots of a job are therefore delivered asynchronously; the first snapshot is still written on the starting request.
<source-channel> (SourceChannelConfig), a channel kind beside <channel> and <derived-channel>:
<channels>
<source-channel name="telegrams" update-interval="250">
<source class="de.haumacher.ehome.monitor.KnxMonitorSource"/>
</source-channel>
</channels>
The source is a ChannelSource instantiated once from the configuration (SourceChannelFactory); Registration subscribe(ViewContext, ChannelSink) is called per displayed view instance and returns the handle ending the subscription. The source publishes its current state into the sink right away and every later state from any thread. SourceViewChannel is a writable value channel (DefaultViewChannel) that additionally receives what its source publishes; it implements ObservingChannel, so the subscription is active only while the view declaring the channel is displayed (attach/detach of the view's root control, the rule observed-types follows as well), and release() ends it when the view instance is destroyed. A source whose subscription fails is logged, and the channel stays a plain value channel.
Tests: TestChannelPublisher, TestSourceChannel, TestJobRunnerDeliveryOrder (rewritten for the asynchronous delivery) in tl-layout-view.
Demo: sidebar entry "Live channel" / "Live-Kanal" of tl-demo-react (demo/live-channel-demo.view.xml) with DemoTickerSource, a simulated telegram feed on the scheduler. Documented in the view-layer chapter channels.md (section "Channels fed by a server-side source"); the <start-job> section of commands.md names the shared publisher.