pywry-orientation
When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing Flask/Streamlit/Dash/Electron code. Use at…
**CRITICAL**: This mode is for **production servers** serving multiple concurrent users. Widgets are stateless. State lives externally. Scale horizontally.
$ npx -y skills add deeleeramone/PyWry --skill deploy --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/deployContext preview
The summary Claude sees to decide when to auto-load this skill.
**CRITICAL**: This mode is for **production servers** serving multiple concurrent users. Widgets are stateless. State lives externally. Scale horizontally.
> **CRITICAL**: This mode is for **production servers** serving multiple concurrent users. > Widgets are stateless. State lives externally. Scale horizontally.
You're creating widgets for a **production SSE server** with:
┌─────────────────┐
│ Load Balancer │
└────────┬────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PyWry Server │ │ PyWry Server │ │ PyWry Server │
│ (SSE #1) │ │ (SSE #2) │ │ (SSE #3) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└────────────────────────┼────────────────────────┘
│
▼
┌───────────────────────┐
│ Shared State Store │
│ (Redis / Database) │
└───────────────────────┘# Include user/session info in widget_id
widget_id = f"user_{user_id}_dashboard"
# Or use session-scoped IDs
widget_id = f"session_{session_id}_chart"
# This makes debugging and monitoring easier# BAD: State in widget
widget_state = {"threshold": 0.5} # Lost on server restart
# GOOD: State in external store
redis.set(f"widget:{widget_id}:state", json.dumps({"threshold": 0.5}))
# On event:
state = json.loads(redis.get(f"widget:{widget_id}:state"))# Implement session timeouts
def cleanup_expired_sessions():
for widget_id in list_widgets():
if is_expired(widget_id):
destroy_widget(widget_id)
redis.delete(f"widget:{widget_id}:*")
# Call on disconnect
events = get_events(widget_id, clear=True)
for e in events:
if e["event_type"] == "pywry:disconnect":
destroy_widget(widget_id)# Don't force dark/light - let users choose
# Store preference in user settings
user_prefs = get_user_preferences(user_id)
update_theme(widget_id, theme=user_prefs.get("theme", "system"))| Component | Why | |-----------|-----| | `Div` | Server-rendered content | | `Button` | Explicit user actions (trackable) | | `RadioGroup`/`TabGroup` | View switching (state trackable) | | `AG Grid` | Large datasets (`show_dataframe`) |
# Events are per-widget # User A's events don't appear in User B's widget events_a = get_events(widget_id_a, clear=True) # Only user A's events events_b = get_events(widget_id_b, clear=True) # Only user B's events
# Monitor active sessions
active_widgets = list_widgets()
print(f"Active sessions: {len(active_widgets)}")
# Check specific widget
if widget_id in active_widgets:
# Widget still active
pass# Send updates to multiple widgets
for widget_id in get_widgets_by_room("market-updates"):
send_event(widget_id, event_type="price:update", data=new_prices)# nginx.conf
upstream pywry_servers {
ip_hash; # Same client → same server
server pywry1:8001;
server pywry2:8001;
server pywry3:8001;
}# Publish updates to all servers
redis.publish("widget:updates", json.dumps({
"widget_id": widget_id,
"event_type": "data:refresh",
"data": new_data
}))
# Servers subscribe and forward
def handle_redis_message(message):
data = json.loads(message)
send_event(data["widget_id"], data["event_type"], data["data"])import asyncio
import json
from pywry import PyWry
from pywry.toolbar import Div, Button, Toolbar
from redis import Redis
app = PyWry()
redis = Redis()
def create_user_dashboard(user_id: str, session_id: str):
"""Create a dashboard widget for a user session."""
widget_id = f"user_{user_id}_{session_id}"
# Load user state from Redis
state = json.loads(redis.get(f"state:{widget_id}") or "{}")
content = Div(
content=f"""
<div id="dashboard">
<h1>Welcome, User {user_id}</h1>
<div id="data-area">Loading...</div>
</div>
""",
component_id="main",
)
widget = app.show(
html=content.build_html(),
height=600,
toolbars=[
Toolbar(
position="top",
items=[
Button(label="Refresh", event="refresh", variant="primary"),
Button(label="Export", event="export"),
Button(label="Logout", event="logout", variant="danger"),
]
)
]
)
# Track session
redis.setex(f"session:{widget_id}", 3600, user_id) # 1 hour TTL
return widget
async def handle_session(widget_id: str):PyWry is a cross-platform app factory, rendering engine and UI toolkit for Python that produces native desktop, web, and notebook experiences from a single API.
Repo: deeleeramone/PyWry
When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing Flask/Streamlit/Dash/Electron code. Use at…
Add OAuth2 authentication to PyWry apps — Google, GitHub, Microsoft, or any OIDC provider.
Autonomous PyWry application building using LLM sampling, elicitation, and progress reporting.
How an agent operates inside a PyWry chat widget — reading user messages, attachments, @-context, tool-call result cards, edit/resend, settings changes.
**STOP. THIS IS THE AUTHORITATIVE REFERENCE FOR ALL COMPONENTS.** **YOU MUST USE THE EXACT EVENT SIGNATURES DOCUMENTED HERE.** **THERE ARE NO EXCEPTIONS. NO…