RFC: Unified Renderer - #633
Open
johnthecat wants to merge 2 commits into
Open
Conversation
This was referenced Sep 8, 2026
johnthecat
force-pushed
the
rfc/unified-renderer
branch
from
September 8, 2026 10:20
80b6f35 to
18b6a6a
Compare
Lead the summary and motivation with the intent, tighten prose to the RFC style rules, drop the chat-specific peer field from the action item, and make the action payload a required Vec<u8> that is empty for a button press.
pgherveou
approved these changes
Sep 8, 2026
pgherveou
left a comment
Collaborator
There was a problem hiding this comment.
please rewrite that as a 1 pager RFC issue, the details of all the types can live in the PR, the RFC is just here to express the high level concepts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC:
docs/rfcs/unified-renderer.md.Third of a four-PR stack: Subscription Typed Interrupt Payload (#631) → Worker Lifecycle (#632) → Unified Renderer (#633) → Input Modality (#634). Based on the worker-lifecycle branch.
Why this RFC exists
A product has one way to show its own information inside a host surface: a
ChatMessageContent::Custommessage, rendered throughChat::custom_message_renderand answered throughChat::action_subscribe. The request is keyed by message, and a press on a tree button is indistinguishable from a press on a button the host drew for anActionsmessage. The Input Modality needs a product-drawn candidate and the Pocket modality a card face. One service with aRenderContextper surface serves all of them instead of a render callback and an action stream per surface.What it defines
Rendererworker service besideChat: a host-initiatedrenderstream ofRendererNodeand a product-sideaction_subscribe. Both carry aRenderContextnaming where the body lives:ChatMessage { room_id, message_id, message_type },InputWidget { candidate_id },PocketCard { card_id }. A new surface is a new variant.{ context, payload }; a surface that needs a content discriminator carries it in its context variant. The action item is{ context, action_id, payload: Vec<u8> }; the payload is empty for a button press and the UTF-8 bytes of the new value for a text-field change.CallError<GenericError>as their interrupt type. Arenderstream opens when the body comes on screen, closes when it leaves, and each item replaces the whole tree. An open stream is one worker reference under the Worker Lifecycle RFC.Chathas nocustom_message_render; aCustommessage renders throughRenderer::renderwith aChatMessagecontext.ChatActionPayload::ActionTriggeredcarries only presses on host-drawnActionsbuttons.Render tree
RendererNodeis the chatCustomRendererNodevocabulary under a surface-neutral name, with these additions and shape changes. Product authors that build trees by hand or through a serializer need to know all of them.New nodes
Image { modifiers, props: ImageProps }.ImageProps.sourceis anImageSource:Bulletin(cid)for a Bulletin chain blob orArchive(path)for a file inside the product's executable archive, so the tree never carries a URL and the host does the fetch.ImageProps.fitis an optionalImageFit(None,Fill,Cover,Contain,ScaleDown) defaulting toFill. An image that cannot be fetched draws as empty space.Effect { props: EffectProps, children }applies one visual effect to its children.Effectis an enum whose variants each name an effect and carry its parameters;Rainbowis the first. The remaining variants are an open question.New modifiers
Opacity(u8), 0 transparent to 100 opaque.BlendingMode(BlendingMode)with the sixteen values common to CSSmix-blend-mode, SwiftUIBlendModeand ComposeBlendMode: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.New shape
Shape::Square, besideRounded(radius)andCircle.Shape changes
children:Spacer,TextFieldandImagehavemodifiersand props only.Effecthaspropsandchildrenbut nomodifiers.Shape::Rounded(Size),Modifier::Height(Size),Width,MinWidth,MinHeight,FillWidth(bool),FillHeight(bool). The SCALE encoding is the same as the one-field struct form.OptionBoolis used directly forenabledandloading; there is noOptionalBoolalias. An absent value leaves the default to the host.Scope
The chat path is implemented here. The
PocketCardcontext is the seam the Pocket modality (#609) adopts when it lands; card rendering is not implemented in this PR.Implementation
Part of this PR.
truapi:api/renderer.rs,v01/renderer.rs(moved fromv01/chat/custom_renderer.rs), versioned envelopes,latestre-exports;Chat::custom_message_renderremovedtruapi-codegengoldens and generated dispatcher, wire table, wasm bridgetruapi-server:render_custom_messagebecomesrenderwith aChatMessagecontext onProductRuntimeControl, native and wasm surfaces,native_renderer.rs@parity/truapiand@parity/truapi-host:renderer.onRender,renderer.actionSubscribe, host runtime plumbing