Engine Architecture / Language Tooling / Systems
TSXLight Renderer: Server-Owned TSX Component Rendering
A server-owned rendering framework where TSX components render into user-specific shells, callbacks travel over sockets, and each user receives an isolated renderer instance.
Abstract
TSXLight Renderer is a React-style rendering engine for web and Electron targets with server-owned component state. It defines a component model with a custom JSX factory, renders component trees to HTML shells, registers callback handlers on the server, and uses socket messages to route client events back into server-owned component instances. Each connected user receives an isolated renderer with its own state, callbacks, page context, and communication channel. The framework also includes a page manager, state save/load hooks, screen-size tracking, render-mode settings, and a Juice Messenger application surface that demonstrates callback-driven rerendering.
I
Motivation
TSXLight asks what a React-like programming model looks like if the server owns the component tree. The browser receives a rendered shell and a small callback bridge, while component instances, state, page transitions, and callbacks remain on the server.
The system defines a distinct rendering boundary from browser-owned React: state can live server-side, callbacks can be represented in generated markup, and separate users can interact with the same application code without sharing renderer state.
The motivation is easiest to understand by contrast. In React, the browser owns component instances and event handlers are client-side closures. In TSXLight, the browser is closer to a terminal: it displays generated markup and reports events, while the server decides which component object receives the event and what the next view should be. That inversion is unusual, but it makes identity, callback serialization, and per-user state isolation the central design problems.
- Primary goal: render TSX-style component trees outside the React runtime.
- Target surfaces: web and Electron shells.
- Demo target: a callback-driven placeholder messenger application.
II
Per-User Renderer Instances
A TSXLight instance is created with a renderer ID and registered with the PageManager. It owns the current DOM template, root component, base app wrapper, renderer ID, and the user-specific output file path or socket channel.
The per-user instance model is the central isolation boundary. Each user can have separate component state, callback registrations, page state, and communication, which prevents one user's rerender path from mutating another user's active page.
Server-owned components create an immediate isolation problem: a single global component tree would make every connected user share clicks and state. TSXLight routes user identity to a renderer ID, renderer ID to active page, and active page to component instance and callback table.
- UserManager maps users to renderer IDs.
- PageManager maps renderer IDs to active page IDs and per-page component instances.
- Each renderer can save states before page transitions and rerender independently.
- Settings can limit duplicate active connections per user.
III
TSX Rendering Pipeline
The renderer walks components and JSX elements recursively, turning the tree into an HTML string. Primitive children become text, component children render through their own rendered children, and JSX elements become DOM strings with serialized props.
Callback props receive special handling. When a prop starts with an event-style name, the renderer registers the function with the CallbackManager, emits a stable callback ID into the markup, and generates client-side code that calls back over the socket when the user triggers the event.
- JSDOM provides the server-side DOM environment.
- Template files provide the initial web or Electron shell.
- Style props are converted from TSX-style objects into CSS text.
- Callback IDs are regenerated during render and stored server-side.
IV
Component Model
A TSXLight component is a server-resident object that can render a JSX-like subtree, hold mutable state, expose lifecycle hooks, and register callbacks. The rendered client output is a projection of that object, not the owner of the object.
type Component = {
props: Record<string, unknown>;
state?: Record<string, unknown>;
render(): TsxNode;
saveState?(): unknown;
loadState?(state: unknown): void;
afterRender?(): void;
};The custom JSX factory creates plain element records rather than React elements. Intrinsic elements become tag records; component constructors become component records; children are stored explicitly so the server renderer can walk the whole tree.
V
Identity and Callback Addressing
A server-owned component tree needs a stable identity model because the client cannot close over JavaScript functions directly. TSXLight solves this by converting event props into callback registrations. The generated markup carries callback identifiers, while the server stores the executable function and its receiver context.
client event | v socket message(user, page, element, event, payload) | v CallbackManager lookup | v invoke server function with stored binding | v mutate component state | v rerender shell for that user
Browser-owned React keeps event handlers as closures already resident in the client runtime. TSXLight handlers are serialized as identities and resolved through server state, so callback routing becomes part of the framework's correctness model.
The implementation regenerates callback IDs during render and clears the active page's callback table during rerender. Old DOM events cannot call handlers for elements that no longer exist. It is a blunt consistency strategy, but the invariant is clear: after a render, only callbacks emitted by that render should be callable.
VI
Page and State Management
TSXLight builds a page system on top of the component tree. Pages are registered by ID with load and unload callbacks, and any renderer can transition to a page by ID. During transitions, the framework saves the outgoing component state, stores the per-renderer page component, and runs lifecycle callbacks.
The state model is intentionally explicit. Components can save and load state between renders, force a rerender without saving, and use the page transition system to move between application surfaces.
- PageManager stores base components and per-renderer component instances.
- State save/load recursively walks component children.
- transitionToPage coordinates unload, state persistence, active-page assignment, render, and load.
- afterRender is the primary lifecycle hook for post-render behavior.
VII
State Persistence Model
The state model is page-scoped and renderer-scoped. A base page definition supplies the component class or component tree, but active component instances belong to a renderer. Before a transition, the outgoing tree can serialize state; after a transition, the incoming page receives either its prior renderer-local state or a fresh instance.
save outgoing page state
run outgoing unload hook
select next page for renderer
load or create renderer-local component instance
render next page shell
run incoming load/afterRender hooksThis model favors explicit ownership over implicit reconciliation. The server can decide when state is saved, when it is discarded, and which renderer receives which page instance.
VIII
Render Algorithm
Rendering is a recursive tree walk with special cases for primitives, arrays, intrinsic tags, and component nodes. The renderer emits escaped text for primitive children, serializes safe HTML attributes for ordinary props, converts style objects into CSS declarations, and diverts event props into the callback registry.
render(node):
if node is null/false: return ""
if node is string/number: return escape(node)
if node is array: return concat(render(child))
if node is component:
instance = resolve or create component instance
return render(instance.render())
if node is intrinsic element:
id = allocate element id
attrs = serialize props except events
callbacks = register event props under id
return "<tag attrs callbacks>" + render(children) + "</tag>"A rerender clears or replaces the callback table for the active page before walking the tree again. That prevents stale event identities from surviving after the DOM shape changes.
IX
Client Communication
The browser or Electron shell receives rendered markup plus a callback bridge. When the user interacts with an element, the shell sends the event identity back to the server. The server looks up the callback under the active user and page, invokes it with the recorded this-binding, and can rerender the component tree.
This pattern makes the client very thin, but it also makes latency and connection management part of the rendering model. It works best for experiments, internal tools, or controlled desktop shells where server-owned state is a feature rather than an accident.
- ServerManager supplies the process port and communication settings.
- CallbackManager stores callbacks by user, page, and callback ID.
- ScreenSizeManager lets server components query user-specific viewport information.
- The Juice Messenger application surface demonstrates state changes and callback-triggered rerendering.
X
Socket Event Protocol
The callback bridge can be reconstructed as a small message protocol. The client sends renderer identity, active page identity, callback identity, event name, and a serialized event payload. The server validates the renderer/page pair, looks up the callback, invokes it, and decides whether to rerender.
{
"type": "callback",
"rendererId": "renderer-7",
"pageId": "inbox",
"callbackId": "cb-42",
"eventName": "click",
"payload": {
"value": "Send",
"clientX": 144,
"clientY": 220
}
}receive message | v validate rendererId -> user/session | v validate pageId == active page | v lookup callbackId in page callback table | v invoke callback with payload | +-- state changed -> rerender and emit shell patch/full shell +-- no change -> acknowledge
XI
Isolation and Failure Boundaries
Per-user renderers are the primary isolation boundary. User identity selects a renderer, the renderer selects an active page, and the active page selects the callback table. A callback message that does not match the current user and page should not be able to invoke another user's component function.
The architecture therefore treats duplicate connections, stale callback IDs, page transitions, and viewport-specific state as renderer-management problems rather than client-rendering problems. The tradeoff fits controlled shells and internal tools where the server is meant to remain the source of truth.
- Callback IDs are scoped by renderer and page, not treated as global capabilities.
- Page transitions clear or replace the active callback surface.
- Screen-size tracking is user-specific, which prevents one viewport from driving another user's layout decisions.
- Connection policy belongs to the server manager rather than to generated markup.
XII
Where This Model Fits
TSXLight is not presented as a replacement for modern client React. It is an experiment in a different ownership model. The design fits best when the server is already the trusted authority, when the UI is controlled, and when a full browser-side state machine would be more liability than benefit: internal tools, desktop shells, demos, admin consoles, or teaching environments.
The same ownership model is a poor fit for latency-sensitive drawing tools, offline-first applications, public high-scale frontends, or interfaces that need rich browser-local interaction between server turns. Every meaningful event has to cross the socket boundary, so network latency and connection lifecycle become part of the rendering contract.
- Server ownership simplifies authority and persistence for controlled shells.
- Callback serialization makes event identity explicit but adds protocol and lifecycle complexity.
- Per-user renderers prevent accidental shared state but require careful cleanup for duplicate connections.
- Full-shell rerendering is understandable, but it lacks the efficiency of browser-side reconciliation.
XIII
Rerender Consistency
Because the server owns the component tree, rerender consistency depends on sequencing. Callback invocation, state mutation, callback-table replacement, HTML generation, and shell delivery must occur as one ordered transaction for a renderer.
- Only the addressed renderer is mutated by a callback transaction.
- The active callback table is regenerated from the rendered tree.
- Page transitions persist outgoing state before replacing the active page.
- Viewport updates are stored per renderer and consumed during subsequent renders.
- Duplicate or stale callbacks are rejected by renderer/page/callback identity checks.
This transaction model is the server-side equivalent of UI reconciliation. Instead of diffing fibers in the browser, TSXLight rebuilds the renderer-owned shell from authoritative server state.
XIV
Results and Design Properties
TSXLight Renderer establishes a complete architecture for TSX-authored, server-owned UI. It separates authoring syntax from React's runtime, renders component trees into user-specific HTML shells, routes event callbacks through sockets, and keeps page state inside renderer instances.
The technical result is an explicit rendering ownership model. The server owns state, callbacks, page transitions, component instances, and rerender decisions; the client owns display and event forwarding. That split makes identity and isolation visible, which is the core contribution of the experiment.
- A JSX-like authoring model can be separated from React's browser runtime assumptions.
- Server-owned component trees require renderer-local callback tables and page-local state.
- Callback serialization is the core design problem once the client becomes an event bridge.
- The architecture fits controlled web/Electron shells where server-owned state is an intentional property.