Skip to content

feat(platform): serve Chat from a JS host as an optional capability - #400

Open
filvecchiato wants to merge 2 commits into
mainfrom
feat/chat-platform-js-host
Open

feat(platform): serve Chat from a JS host as an optional capability#400
filvecchiato wants to merge 2 commits into
mainfrom
feat/chat-platform-js-host

Conversation

@filvecchiato

Copy link
Copy Markdown
Collaborator

Closes #383.

  • New OptionalPlatform super-trait in truapi-platform lists capabilities a host may omit. Codegen reads it and emits chat?: ChatPlatform on HostCallbacks, optional RawCallbacks members, and get_optional_function bindings — so omitting chat keeps chat_platform: None and the existing Unsupported answer.
  • The worker init message now carries which optional capabilities the main-thread host serves, so the core sees the same set either side of the boundary.
  • ChatPlatform methods renamed to create_chat_room / post_chat_message / subscribe_chat_rooms: the callback namespace is flat, and postMessage was ambiguous. subscribe_chat_rooms now yields a Result like every other platform stream.

@filvecchiato
filvecchiato requested a review from a team August 14, 2026 13:17
navigation,
notifications,
// ...required groups...
chat, // optional: leave it out and chat products get `Unsupported`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A JS host that serves chat can create rooms, post messages and list rooms, but can never deliver an incoming message. Nothing in a wasm build can publish a chat action: ChatConnection::publish_action is compiled out on wasm, and its only caller lives in the native module, which is also compiled out. So Chat/action_subscribe returns a subscription that yields nothing and never ends, which a product cannot tell apart from a quiet room. Could you say here that JS-host chat is outbound-only for now, and open a follow-up issue for the inbound path. Custom-message rendering is native-only too.

.subscribe_rooms(&self.product)
.map(HostChatListSubscribeItem::V1),
.subscribe_chat_rooms(&self.product)
.filter_map(|item| async { item.ok().map(HostChatListSubscribeItem::V1) }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You widened subscribe_chat_rooms to yield a Result, but this line drops the error with no log. One failure from the host leaves the product's room list frozen on its last value forever, with nothing recorded anywhere to say why. Please log it before returning None, the way lookup_subscribe does in this same file. Theme::subscribe at let stream = self.platform.subscribe_theme().filter_map(|item| async { drops errors the same way and would benefit from the same line.

/// so this is never invoked; it throws rather than returning a value the
/// decoder would misread.
fn missing_callback(name: &str) -> Function {
Function::new_no_args(&format!(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function::new_no_args builds a function from a string of source code, the same way eval does. Browsers block that when the page runs under a Content Security Policy that does not allow unsafe-eval, and they can block it while still allowing WebAssembly to run. This line runs at startup for every host that leaves chat out, three times, so the whole host runtime would fail to start, not just chat. Please build the stub as a Rust closure that returns an error instead of from a source string. noop_function just above does the same thing and can move too.

) -> BoxStream<'static, Result<HostChatListSubscribeItem, GenericError>>;
}

/// Combined platform interface. A host must provide all capability traits.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc is now wrong. It is copied into the generated TypeScript and lands directly above chat?: ChatPlatform, so a host author reads "must provide all capability traits" over an interface with an optional member. Could you reword it, something like "A host must provide every capability trait listed here. Members marked optional may be omitted; the core answers their product calls with Unsupported. See [OptionalPlatform]." Saying "members marked optional" rather than only naming the Rust trait matters, because OptionalPlatform does not exist on the TypeScript side. I made the edit and reran codegen to confirm it comes through cleanly.

provider.dispose();
});

it("reports the chat capability to the worker when the host serves it", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This covers the init message carrying chat: true, but nothing covers the worker acting on it. No test in the package references createWorkerRawCallbacks or startRawSubscription, so the gate that installs or omits chatRawCallbacks, and the new callbacks.subscribeChatRooms?.(...) path, are both untested. Could you add three cases: chat omitted when no capability is reported, chat proxied when it is, and startRawSubscription("subscribeChatRooms", ...) returning undefined when chat is absent.

writedoc!(
out,
r#"
/// Optional capabilities the main-thread host actually serves. A

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This emits Rust /// comments into TypeScript. They are legal but invisible: editors do not show them on hover and they will not reach generated docs, which is a shame since this is exactly the text a host author needs. Everything else in these files uses /** ... */, including the /** Whether the host serves this capability. */ line you generate a few lines below. Please emit JSDoc here and at the matching block in emit_raw_callbacks, which starts /// Byte-oriented callback surface the WASM core invokes. Members of an. There is already a render_jsdoc helper in this file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A JS host cannot serve chat: executionKind: "Chat" is accepted, but every chat call returns Unsupported

2 participants