Repository navigation
feat(widgets): shim show_widget html onto upstream HtmlRender - #408
Conversation
Phil decision 2026-10-06 (hive/nx-nexi#31, pj/nexi-distribution#611): - format=html publishes via HtmlRender.publish (one HTML renderer) while keeping the fragment on the widget attachment for back-compat - svg and other formats stay on the widget tier - accept intent (builder seam; not yet model-wired) with widget_code bypass - preserve widget CSP connect-src 'none'; do not use publicProxy - web: HtmlRenderFrame when htmlRender present and no tool bridge
Thread transfer impact
This comment will update automatically after the next completed run. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c53fe8a16
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| export function authorWidgetFromIntent( | ||
| _input: AuthorWidgetFromIntentInput, | ||
| ): Effect.Effect<AuthorWidgetFromIntentResult, string> { | ||
| return Effect.fail(T3TEAM_WIDGET_BUILDER_UNAVAILABLE); |
There was a problem hiding this comment.
Do not advertise an intent path that always fails
Any caller following the new model-facing recommendation to provide only intent reaches this function, which unconditionally returns T3TEAM_WIDGET_BUILDER_UNAVAILABLE; consequently the newly preferred/default path can never display a widget. Either keep intent out of the MCP/catalog/workflow contracts until the builder exists, or implement the builder before directing agents to use it.
Useful? React with 👍 / 👎.
| // Keep the srcdoc + postMessage bridge when the widget can call tools. | ||
| const tools = widget.capabilities?.tools ?? []; | ||
| return tools.length === 0; |
There was a problem hiding this comment.
Preserve the widget runtime on the HTML shim path
For the common HTML widget with no tool allowlist, this selects HtmlRenderFrame instead of the widget srcdoc, but the upstream HTML bootstrap provides neither sendPrompt nor the t3w-icon-* sprite. Newly published widgets that use the documented sendPrompt(...) API therefore throw at interaction time, and the explicitly recommended sprite icons render blank; checking only capabilities.tools does not establish that the widget is static. The shim must inject/preserve those widget facilities or retain the widget renderer for markup that depends on them.
Useful? React with 👍 / 👎.
| if (parsed.format === "html" && deps.htmlRender) { | ||
| htmlRenderRef = yield* publishHtmlShim({ | ||
| threadId: deps.threadId, | ||
| parsed, | ||
| htmlRender: deps.htmlRender, | ||
| }); |
There was a problem hiding this comment.
Register shim attachments for thread cleanup
A successful publish writes an .html file into the thread attachment store, but thread/project deletion discovers HTML files only from ProjectionStore.getThreadAttachmentIds via threadHtmlRenderAttachmentIds, whose htmlRenderFromToolItem filter accepts html_render tool calls—not these t3_show_widget artifacts. Thus every shimmed widget leaves its file behind after thread deletion; a subsequent recordArtifact failure also leaks the just-published file immediately. Include these artifact references in attachment discovery or remove the published file when the artifact cannot be recorded.
Useful? React with 👍 / 👎.
| <HtmlRenderFrame | ||
| environmentId={threadRef.environmentId} | ||
| htmlRender={htmlRender} | ||
| onOpen={onOpenHtmlRender ?? (() => undefined)} |
There was a problem hiding this comment.
Wire or hide the HTML render open action
All current T3TeamWidgetBlock call sites omit onOpenHtmlRender, so every shimmed widget passes a no-op to HtmlRenderFrame. The frame still renders its “Open in panel” button, but clicking it does nothing for every widget. Thread the existing file-preview callback into these rows, or suppress the control when opening is unsupported.
Useful? React with 👍 / 👎.
Summary
Implements Phil's HARD decision (2026-10-06):
t3_show_widgetformat: "html"becomes a shim onto upstreamHtmlRender.publish, so there is one HTML renderer. Upstreamhtml_preview/html_renderand forkt3_show_widgetboth stay. Other formats (svg, mdx/tsx, …) stay on the widget tier.Issues: hive/nx-nexi#31 · pj/nexi-distribution#611
Flow
intent → builder → widgetCode(format) → html: HtmlRender shim | other: widget tierRaw
widget_codebypasses the builder (required for deterministic workflow replay).What changed
connect-src 'none') and callsHtmlRender.publish; attachment keeps fragment + optionalhtmlRenderref. Svg unchanged. HtmlRender provided to the tool broker layer.intentaccepted; intent-only hitsauthorWidgetFromIntent(not model-wired yet — clear error, no invented HTML).widget_codebypasses.T3TeamWidgetBlockusesHtmlRenderFramewhenhtmlRenderis present and there is no tool allowlist (bridge stays on srcdoc). Legacy html withouthtmlRenderstill uses srcdoc.Out of scope
publicProxy(intentionally not used)Tests
Packs
No pack pin PRs in this change — packs already pin fork main (
b6b9bfb1). They pick this up on the next pin after merge.Do not merge until Phil says go.