Skip to content
Development
Skill

/deploy

**CRITICAL**: This mode is for **production servers** serving multiple concurrent users. Widgets are stateless. State lives externally. Scale horizontally.

From plugin
pywry
9918 skills1 agent2 commands1 MCP

Claude Code

$ npx -y skills add deeleeramone/PyWry --skill deploy --agent claude-code

SKILL.md

deploy.SKILL.md

Production Deploy Mode

> **CRITICAL**: This mode is for **production servers** serving multiple concurrent users. > Widgets are stateless. State lives externally. Scale horizontally.

What Deploy Mode IS

You're creating widgets for a **production SSE server** with:

  • Multiple concurrent users/sessions
  • Stateless widget creation
  • Horizontal scaling support
  • Persistent widget URLs

Architecture

                         ┌─────────────────┐
                         │  Load Balancer  │
                         └────────┬────────┘
                                  │
         ┌────────────────────────┼────────────────────────┐
         │                        │                        │
         ▼                        ▼                        ▼
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│  PyWry Server   │      │  PyWry Server   │      │  PyWry Server   │
│    (SSE #1)     │      │    (SSE #2)     │      │    (SSE #3)     │
└─────────────────┘      └─────────────────┘      └─────────────────┘
         │                        │                        │
         └────────────────────────┼────────────────────────┘
                                  │
                                  ▼
                      ┌───────────────────────┐
                      │   Shared State Store  │
                      │   (Redis / Database)  │
                      └───────────────────────┘

Key Principles

Widgets Are Stateless

  • Widget instances don't persist server state
  • Store state externally (Redis, database, etc.)
  • Any server can handle any widget update

Session Management

  • Each user gets a unique widget_id
  • Map widget_id to user session externally
  • Implement timeouts and cleanup

Horizontal Scaling

  • Servers behind load balancer
  • SSE connections are per-server (sticky sessions may help)
  • Widget operations are idempotent

Best Practices

1. Widget IDs - Use Meaningful IDs

# 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

2. State - Store Externally

# 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"))

3. Cleanup - Destroy Widgets

# 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)

4. Theme - User Choice

# 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"))

Recommended Components

| Component | Why | |-----------|-----| | `Div` | Server-rendered content | | `Button` | Explicit user actions (trackable) | | `RadioGroup`/`TabGroup` | View switching (state trackable) | | `AG Grid` | Large datasets (`show_dataframe`) |

Multi-User Considerations

No Cross-Talk

# 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

Session Monitoring

# 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

Broadcast Updates

# 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)

Scaling Patterns

Sticky Sessions (Simple)

# nginx.conf
upstream pywry_servers {
    ip_hash;  # Same client → same server
    server pywry1:8001;
    server pywry2:8001;
    server pywry3:8001;
}

Redis Pub/Sub (Advanced)

# 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"])

Code Example

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):
Read more
@deeleeramone
Ships withpywry

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.

9918 skills1 agent1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
99
Star history of deeleeramone/PyWry on GitHub, a chart by star-history.com

Other skills on pywry.

pywry-orientation

pywry-orientation

When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing…

@deeleeramone@deeleeramoneInstall Skill
chat_agent

chat_agent

How an agent operates inside a PyWry chat widget — reading user messages, attachments, @-context, tool-call result cards,…

@deeleeramone@deeleeramoneInstall Skill
component_reference

component_reference

**STOP. THIS IS THE AUTHORITATIVE REFERENCE FOR ALL COMPONENTS.** **YOU MUST USE THE EXACT EVENT SIGNATURES DOCUMENTED HERE.**…

@deeleeramone@deeleeramoneInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about deploy.

Which coding agents can use deploy?
deploy installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.