From 7bd42ab45c35da6738bfb66c8014c37024fd3ec3 Mon Sep 17 00:00:00 2001
From: deeleeramone <>
Date: Sat, 16 May 2026 20:44:14 -0700
Subject: [PATCH 1/5] fix chart handler getting swallowed
---
pywry/examples/pywry_demo_deepagent_nvidia.py | 55 +++++++++----------
pywry/pywry/frontend/src/chat-handlers.js | 12 +++-
pywry/pywry/frontend/src/main.js | 12 ++++
pywry/pywry/frontend/src/system-events.js | 9 +++
pywry/pywry/templates.py | 6 ++
pywry/pywry/widget.py | 6 ++
6 files changed, 69 insertions(+), 31 deletions(-)
diff --git a/pywry/examples/pywry_demo_deepagent_nvidia.py b/pywry/examples/pywry_demo_deepagent_nvidia.py
index 6da1394..b655f7e 100644
--- a/pywry/examples/pywry_demo_deepagent_nvidia.py
+++ b/pywry/examples/pywry_demo_deepagent_nvidia.py
@@ -84,22 +84,19 @@ def new_event_loop(self) -> asyncio.AbstractEventLoop:
from pywry.tvchart import build_tvchart_toolbars # noqa: E402
-DEFAULT_MODEL = "qwen/qwen3-coder-480b-a35b-instruct"
-
-# Preferred tool-capable chat models exposed through NVIDIA NIM, in
-# priority order. The first model that the live ``ChatNVIDIA.
-# get_available_models()`` lookup actually returns is picked as the
-# default. Llama variants are intentionally excluded — they under-
-# perform on strict-format tool-calling compared to the models below.
+DEFAULT_MODEL = "deepseek-ai/deepseek-v4-pro"
+
+# Small curated set of currently-hosted tool-capable chat models on
+# NVIDIA NIM (May 2026). Kept intentionally short — every entry must
+# (a) support tool/function calling, (b) be currently served (no 4xx
+# at inference), and (c) be strong enough for the chart-agent loop.
+# The first entry that ``ChatNVIDIA.get_available_models()`` confirms
+# is live becomes the default; the rest populate the model dropdown.
PREFERRED_TOOL_MODELS = [
+ "deepseek-ai/deepseek-v4-pro",
+ "nvidia/nemotron-3-super-120b-a12b",
"qwen/qwen3-coder-480b-a35b-instruct",
- "deepseek-ai/deepseek-v3.1",
- "deepseek-ai/deepseek-r1",
- "moonshotai/kimi-k2-instruct-0905",
"openai/gpt-oss-120b",
- "qwen/qwen3-235b-a22b-instruct-2507",
- "zai-org/glm-4.5-air",
- "mistralai/mistral-nemotron",
]
CHART_SYSTEM_PROMPT = """\
@@ -304,26 +301,28 @@ def stop() -> None:
def _fetch_nvidia_models() -> list[str]:
- """Fetch available tool-capable chat models from NVIDIA NIM."""
+ """Return the curated tool-capable models actually live on NIM.
+
+ Filters ``PREFERRED_TOOL_MODELS`` against the live
+ ``ChatNVIDIA.get_available_models()`` catalog so the dropdown only
+ ever shows models we've vetted AND that NVIDIA is currently serving.
+ Falls back to the full curated list on lookup failure so the UI is
+ never empty.
+ """
try:
from langchain_nvidia_ai_endpoints import ChatNVIDIA
- available = ChatNVIDIA.get_available_models()
- model_ids = sorted(
+ live_ids = {
m.id
- for m in available
+ for m in ChatNVIDIA.get_available_models()
if getattr(m, "model_type", None) == "chat" and getattr(m, "supports_tools", False)
- )
- if not model_ids:
- return [DEFAULT_MODEL]
- for preferred in PREFERRED_TOOL_MODELS:
- if preferred in model_ids:
- model_ids = [preferred, *[m for m in model_ids if m != preferred]]
- break
- if model_ids:
- return model_ids
- except Exception:
- return [DEFAULT_MODEL]
+ }
+ except Exception as exc:
+ print(f"[deepagent] NVIDIA model catalog lookup failed ({exc}); using curated list.")
+ return list(PREFERRED_TOOL_MODELS)
+
+ curated_live = [m for m in PREFERRED_TOOL_MODELS if m in live_ids]
+ return curated_live or list(PREFERRED_TOOL_MODELS)
def main() -> None:
diff --git a/pywry/pywry/frontend/src/chat-handlers.js b/pywry/pywry/frontend/src/chat-handlers.js
index ee87042..be55d1a 100644
--- a/pywry/pywry/frontend/src/chat-handlers.js
+++ b/pywry/pywry/frontend/src/chat-handlers.js
@@ -1878,12 +1878,18 @@ function initChatHandlers(container, pywry) {
// they try to reconnect, which is the intended behaviour.
var html = data.html || data.content || '';
if (data.widgetId && data.revision) {
+ // NOTE: tag literals are split so this script can be safely inlined
+ // into a host document's
without a regex-based head/script
+ // extractor (see pywry/__main__.py) terminating early on a stray
+ // closing tag inside this string.
+ var headOpen = '<' + 'head>';
+ var headClose = '' + 'head>';
var meta = '';
// Insert meta tag into if present, else as first element.
- if (html.indexOf('') !== -1) {
- html = html.replace('', '' + meta);
+ if (html.indexOf(headOpen) !== -1) {
+ html = html.replace(headOpen, headOpen + meta);
} else if (html.indexOf('') !== -1) {
- html = html.replace('', '' + meta + '');
+ html = html.replace('', '' + headOpen + meta + headClose);
} else {
html = meta + html;
}
diff --git a/pywry/pywry/frontend/src/main.js b/pywry/pywry/frontend/src/main.js
index a7b843c..fba51d7 100644
--- a/pywry/pywry/frontend/src/main.js
+++ b/pywry/pywry/frontend/src/main.js
@@ -23,6 +23,12 @@ window.pywry = {
htmlEl.classList.add('dark', 'pywry-theme-dark');
}
document.getElementById('app').innerHTML = html;
+ if (typeof initToolbarHandlers === 'function') {
+ initToolbarHandlers(document, window.pywry);
+ }
+ if (typeof initChatHandlers === 'function') {
+ initChatHandlers(document, window.pywry);
+ }
window.pywry.sendEvent('content:ready', { timestamp: Date.now() });
},
@@ -231,6 +237,12 @@ function registerBuiltinHandlers() {
} else {
document.body.innerHTML = data.html;
}
+ if (typeof initToolbarHandlers === 'function') {
+ initToolbarHandlers(document, window.pywry);
+ }
+ if (typeof initChatHandlers === 'function') {
+ initChatHandlers(document, window.pywry);
+ }
}
});
diff --git a/pywry/pywry/frontend/src/system-events.js b/pywry/pywry/frontend/src/system-events.js
index 88a1a84..789f64d 100644
--- a/pywry/pywry/frontend/src/system-events.js
+++ b/pywry/pywry/frontend/src/system-events.js
@@ -119,6 +119,9 @@
window.pywry.on('pywry:set-content', function(data) {
window.pywry.setContent(data);
+ if (typeof initChatHandlers === 'function') {
+ initChatHandlers(document, window.pywry);
+ }
});
window.pywry.on('pywry:refresh', function() {
@@ -224,6 +227,12 @@
} else {
document.body.innerHTML = data.html;
}
+ if (typeof initToolbarHandlers === 'function') {
+ initToolbarHandlers(document, window.pywry);
+ }
+ if (typeof initChatHandlers === 'function') {
+ initChatHandlers(document, window.pywry);
+ }
}
});
diff --git a/pywry/pywry/templates.py b/pywry/pywry/templates.py
index 7abfc8c..8c72c34 100644
--- a/pywry/pywry/templates.py
+++ b/pywry/pywry/templates.py
@@ -834,5 +834,11 @@ def build_content_update_script(html_content: str) -> str:
}} else {{
document.body.innerHTML = '' + {escaped_html} + '
';
}}
+ if (typeof initToolbarHandlers === 'function') {{
+ initToolbarHandlers(document, window.pywry);
+ }}
+ if (typeof initChatHandlers === 'function') {{
+ initChatHandlers(document, window.pywry);
+ }}
}})();
"""
diff --git a/pywry/pywry/widget.py b/pywry/pywry/widget.py
index 021a571..a5a621c 100644
--- a/pywry/pywry/widget.py
+++ b/pywry/pywry/widget.py
@@ -658,6 +658,9 @@ def _get_aggrid_widget_esm() -> str:
if (event.type === 'pywry:update-html' && event.data && event.data.html) {
container.innerHTML = event.data.html;
initToolbarHandlers(container, pywry);
+ if (typeof initChatHandlers === 'function') {
+ initChatHandlers(container, pywry);
+ }
}
const inlineHandledEvents = ['pywry:update-theme', 'pywry:inject-css', 'pywry:remove-css',
@@ -1256,6 +1259,9 @@ def _get_widget_esm() -> str:
if (event.type === 'pywry:update-html' && event.data && event.data.html) {
container.innerHTML = event.data.html;
initToolbarHandlers(container, pywry);
+ if (typeof initChatHandlers === 'function') {
+ initChatHandlers(container, pywry);
+ }
}
const inlineHandledEvents = ['pywry:update-theme', 'pywry:inject-css', 'pywry:remove-css',
From 9fcbad5829b25c1bdbfafb702cf8d16bcadcfab0 Mon Sep 17 00:00:00 2001
From: deeleeramone <>
Date: Sat, 16 May 2026 20:46:41 -0700
Subject: [PATCH 2/5] version bump
---
claude/plugins/pywry/.claude-plugin/marketplace.json | 2 +-
claude/plugins/pywry/.claude-plugin/plugin.json | 2 +-
pywry/pyproject.toml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/claude/plugins/pywry/.claude-plugin/marketplace.json b/claude/plugins/pywry/.claude-plugin/marketplace.json
index b87a203..92f6821 100644
--- a/claude/plugins/pywry/.claude-plugin/marketplace.json
+++ b/claude/plugins/pywry/.claude-plugin/marketplace.json
@@ -9,7 +9,7 @@
"name": "pywry",
"source": "./",
"description": "Native Claude Code integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.",
- "version": "0.1.0",
+ "version": "0.1.1",
"author": {
"name": "PyWry",
"email": "pywry2@gmail.com",
diff --git a/claude/plugins/pywry/.claude-plugin/plugin.json b/claude/plugins/pywry/.claude-plugin/plugin.json
index 79812f9..10bc786 100644
--- a/claude/plugins/pywry/.claude-plugin/plugin.json
+++ b/claude/plugins/pywry/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "pywry",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Native Claude Code integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.",
"author": {
"name": "PyWry",
diff --git a/pywry/pyproject.toml b/pywry/pyproject.toml
index d091de4..5860c74 100644
--- a/pywry/pyproject.toml
+++ b/pywry/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "pywry"
-version = "2.0.1"
+version = "2.0.2"
description = "A lightweight and blazingly fast, cross-platform, WebView rendering engine and desktop UI toolkit for Python. Batteries included."
authors = [{ name = "PyWry", email = "pywry2@gmail.com" }]
license = { text = "Apache 2.0" }
From bb35e20690a23d2dbc77e7bf4db908f428bad9f4 Mon Sep 17 00:00:00 2001
From: deeleeramone <>
Date: Sat, 16 May 2026 20:50:03 -0700
Subject: [PATCH 3/5] claude marketplace version
---
claude/.claude-plugin/marketplace.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/claude/.claude-plugin/marketplace.json b/claude/.claude-plugin/marketplace.json
index 4550ea9..5f832dd 100644
--- a/claude/.claude-plugin/marketplace.json
+++ b/claude/.claude-plugin/marketplace.json
@@ -9,7 +9,7 @@
"name": "pywry",
"source": "./plugins/pywry/",
"description": "Native Claude Code integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.",
- "version": "0.1.0",
+ "version": "0.1.1",
"author": {
"name": "PyWry",
"email": "pywry2@gmail.com",
From 52b8f6fcf8c72682cc3bfbc2102ca93597e2ef95 Mon Sep 17 00:00:00 2001
From: deeleeramone <>
Date: Sat, 16 May 2026 20:54:42 -0700
Subject: [PATCH 4/5] claude marketplace version
---
claude/desktop-extension/manifest.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/claude/desktop-extension/manifest.json b/claude/desktop-extension/manifest.json
index 7da9568..5ab7a54 100644
--- a/claude/desktop-extension/manifest.json
+++ b/claude/desktop-extension/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": "0.4",
"name": "pywry",
"display_name": "PyWry",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Native Claude Desktop integration for PyWry — MCP tools for generating and rendering HTML components, chat artifacts, and building native, web, or Jupyter applications with live preview. Built-in support for AgGrid, Plotly, TradingView, and more.",
"long_description": "PyWry is a cross-platform rendering engine and desktop UI toolkit for Python. This Desktop Extension bundles the PyWry MCP server (66+ tools) so Claude Desktop can scaffold widgets, render Plotly charts, drive AG Grid tables, build TradingView lightweight charts, and produce streaming chat artifacts — all delivered as embedded HTML resources viewable directly in the artifact pane. The `uv` runtime resolves `pywry[mcp]` from PyPI on first launch; no manual `pip install` is required.",
"author": {
From edc010e6f4ed80769f77cdecadc751e6ffc7f1fb Mon Sep 17 00:00:00 2001
From: deeleeramone <>
Date: Sat, 16 May 2026 20:56:56 -0700
Subject: [PATCH 5/5] claude changelog
---
claude/plugins/pywry/CHANGELOG.md | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/claude/plugins/pywry/CHANGELOG.md b/claude/plugins/pywry/CHANGELOG.md
index 63a4cd7..89a8a08 100644
--- a/claude/plugins/pywry/CHANGELOG.md
+++ b/claude/plugins/pywry/CHANGELOG.md
@@ -3,6 +3,25 @@
Versioning follows [semver](https://semver.org/). Format is a feature
list per release — not a delta.
+## 0.1.1 — chat-handler regression fix
+
+**Chat widget reliability.** Fixed a regression where the desktop chat
+panel rendered empty (no welcome message, dead buttons) after a
+`set_content` IPC swap. Root cause: `chat-handlers.js` contained a
+literal `''` string inside its iframe meta-tag injection
+helper. When inlined into the host document's ``, the desktop
+subprocess's regex-based head-script extractor terminated early on
+the stray tag, dropping the chat-handlers `