Content Assembly¶
When you call app.show(), PyWry doesn't just render your HTML. It assembles a complete document from multiple components — base styles, the JavaScript bridge, your custom CSS and scripts, toolbar layouts, library code, and your content. This page explains exactly what gets built and in what order.
The Assembly Pipeline¶
Here's what happens when app.show(content) is called in native window mode (the most complete path):
- Normalize content → HtmlContent
- Build WindowConfig (title, dimensions, theme, libraries)
- Resolve window label
- Build all HTML components
- CSP meta tag
- Base styles — pywry.css + toast.css
- Global CSS from AssetSettings
- Custom CSS — inline_css + css_files
- Library scripts — Plotly.js / AG Grid
- JSON data injection
- Init script — bridge, theme, events
- Global + custom scripts
- Toolbar layout wrapping
- Modal HTML + scripts
- Detect complete doc vs. fragment
- Assemble final HTML document
- Send to rendering path (native / notebook / browser)
What Goes Into <head>¶
The <head> section is built from these components, injected in this exact order:
1. Content Security Policy¶
A <meta> CSP tag that controls which resources the page can load. Built from your SecuritySettings:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' 'unsafe-inline' 'unsafe-eval'; ...">
2. Base Styles¶
PyWry's built-in CSS:
pywry.css— Layout system, container styles, theme variables, scrollbar styling, the.pywry-containerwrapper, and toolbar/modal base layouttoast.css— Toast notification positioning and animations- Custom theme CSS — If
ThemeSettings.css_fileis set, that file is appended
<style>/* pywry.css — layout, theme vars, containers */</style>
<style>/* toast.css — notifications */</style>
<style>/* optional: your theme.css override */</style>
3. Global CSS¶
CSS files specified in AssetSettings.css_files (from your pywry.toml or pyproject.toml). These apply to every window, not just one:
Each <style> tag gets a hash-based id so the hot reload system can target it precisely.
4. Custom CSS (from HtmlContent)¶
Your per-window CSS, in this order:
HtmlContent.inline_css→<style id="pywry-inline-css">...</style>- Each file in
HtmlContent.css_files→<style id="hash-xxx">...</style>
5. Library Scripts¶
If include_plotly=True or include_aggrid=True, the full library JavaScript is included:
- Plotly:
plotly.min.js+plotly-defaults.js(configures default templates, themes) - AG Grid:
ag-grid-community.min.js+ag-grid-defaults.js(base grid setup) + AG Grid CSS
These are bundled assets loaded from disk — not CDN URLs — so they work offline.
6. JSON Data¶
If HtmlContent.json_data is set, it's serialized and injected:
Uses a custom encoder that handles numpy arrays, datetime objects, and other Python types.
7. Initialization Script¶
This is the largest single injection — the core JavaScript bridge built by build_init_script():
<script>
window.__PYWRY_LABEL__ = 'my-window'; // Window identity
// PyWry Bridge (window.pywry)
// .emit(), .on(), .off(), .result(), .dispatch(), ._trigger()
// System Event Handlers
// pywry:inject-css, pywry:remove-css, pywry:set-style,
// pywry:set-content, pywry:download, pywry:refresh, pywry:navigate
// Toast Notification System
// window.pywry.toast(), alert types, positioning, animations
// Tooltip Manager
// data-tooltip attribute handling
// Theme Manager
// pywry:update-theme handler, CSS class toggling
// Event Bridge
// Tauri IPC listeners (native) or WebSocket handlers (browser)
// Routes events between Python and JS
// Toolbar Bridge
// Connects toolbar component events to window.pywry.emit()
// Cleanup Handler
// SecretInput cleanup, page unload handling
// Hot Reload (when enabled)
// WebSocket file watcher, CSS injection, page refresh
</script>
8. Toolbar Script¶
If toolbars are present, the toolbar component JavaScript is included. This handles rendering toolbar items (buttons, inputs, selects, etc.) and wiring their change/click events to window.pywry.emit().
9. Modal Scripts¶
If modals are present, the modal system JavaScript is included — open/close/toggle logic, backdrop handling, and event wiring.
10. Global Scripts¶
JavaScript files from AssetSettings.script_files — global scripts that apply to every window.
11. Custom Scripts¶
Each file in HtmlContent.script_files is injected as a <script> tag.
12. Custom Init¶
HtmlContent.init_script runs last — it's the final <script> tag in the document. By the time it executes, everything else is available: window.pywry, window.json_data, all CSS applied, all libraries loaded.
What Goes Into <body>¶
Content Wrapping¶
Your HTML content is placed inside a container div:
Toolbar Layout¶
When toolbars are present, wrap_content_with_toolbars() wraps your content in a flexbox layout structure:
The actual HTML structure:
<div class="pywry-layout">
<div class="pywry-header"><!-- header toolbars --></div>
<div class="pywry-body">
<div class="pywry-left"><!-- left toolbars --></div>
<div class="pywry-center">
<div class="pywry-top"><!-- top toolbars --></div>
<div class="pywry-content">
<div class="pywry-scroll-container">
<!-- YOUR HTML CONTENT -->
<div class="pywry-inside"><!-- inside toolbars --></div>
</div>
</div>
<div class="pywry-bottom"><!-- bottom toolbars --></div>
</div>
<div class="pywry-right"><!-- right toolbars --></div>
</div>
<div class="pywry-footer"><!-- footer toolbars --></div>
</div>
<div class="pywry-toast-container pywry-toast-container--top-right"></div>
Even without toolbars, your content is wrapped in the pywry-content → pywry-scroll-container structure, plus the toast container.
Modal Injection¶
Modal HTML is injected before </body>. Each modal is a hidden overlay that can be opened with modal:open:<id> events:
<!-- modal overlays, hidden by default -->
<div class="pywry-modal-overlay" id="modal-settings" style="display:none">
<div class="pywry-modal">
<div class="pywry-modal-header">Settings</div>
<div class="pywry-modal-body"><!-- modal items --></div>
</div>
</div>
</body>
Fragment vs. Complete Document¶
PyWry detects which type of HTML you provided:
Fragment (no <!DOCTYPE or <html> tag):
PyWry builds the entire document around it — <!DOCTYPE html>, <html> with theme class, <head> with all injections, <body> with .pywry-container wrapping.
Complete document (starts with <!DOCTYPE or <html>):
PyWry injects all assets before the </head> tag and adds the theme class to the <html> tag. Your document structure is preserved. Modals are injected before </body>.
Theme Integration¶
The <html> tag always gets a theme class:
pywry-theme-dark— dark mode (default)pywry-theme-light— light mode
This class is used by pywry.css to set CSS custom properties (colors, backgrounds, borders). The theme manager JavaScript handles runtime toggling via the pywry:update-theme event.
Additionally, PyWry auto-corrects AG Grid and Plotly themes to match the window theme:
ag-theme-alpine→ag-theme-alpine-darkwhen dark modetemplate: 'plotly_white'→template: 'plotly_dark'when dark mode
Asset Loading¶
The AssetLoader resolves all file paths and caches file contents:
AssetLoader
├─ base_dir: Path (defaults to cwd)
├─ load_css(path) → reads file, caches content, generates hash ID
├─ load_script(path) → reads file, caches content
└─ get_asset_id(path) → deterministic ID for hot reload targeting
Paths in css_files and script_files are resolved relative to base_dir. If AssetSettings.path is set in your config, that becomes the base directory for global assets.
The Complete Picture¶
Putting it all together, here's a concrete example. Given:
from pywry import PyWry, HtmlContent, Toolbar, Button
app = PyWry()
content = HtmlContent(
html="<div id='output'>Ready</div>",
css_files=["styles/app.css"],
inline_css="#output { font-size: 1.2em; }",
json_data={"version": "2.0"},
init_script="document.getElementById('output').textContent = 'v' + window.json_data.version;",
)
toolbar = Toolbar(position="top", items=[
Button(label="Run", event="app:run"),
])
app.show(content, title="My App", toolbars=[toolbar])
PyWry assembles:
<!DOCTYPE html>
<html lang="en" class="pywry-native pywry-theme-dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="...">
<title>My App</title>
<!-- 1. Base styles -->
<style>/* pywry.css */</style>
<style>/* toast.css */</style>
<!-- 2. Global CSS (from config) -->
<!-- (none in this example) -->
<!-- 3. Custom CSS -->
<style id="pywry-inline-css">#output { font-size: 1.2em; }</style>
<style id="css-a1b2c3">/* styles/app.css content */</style>
<!-- 4. Libraries (none - no Plotly/AG Grid) -->
<!-- 5. JSON data -->
<script>window.json_data = {"version": "2.0"};</script>
<!-- 6. Init script (bridge, events, theme, toolbars) -->
<script>
window.__PYWRY_LABEL__ = 'pywry-abc12345';
// ... PyWry bridge, system events, theme manager, event bridge ...
</script>
<!-- 7. Toolbar component script -->
<script>/* toolbar rendering + event wiring */</script>
<!-- 8. Custom init (runs last) -->
<script>document.getElementById('output').textContent = 'v' + window.json_data.version;</script>
</head>
<body>
<!-- Toolbar layout wrapping -->
<div class="pywry-layout">
<div class="pywry-body">
<div class="pywry-center">
<div class="pywry-top">
<!-- Top toolbar with Run button -->
<div class="pywry-toolbar pywry-toolbar--top">
<button class="pywry-btn" data-event="app:run">Run</button>
</div>
</div>
<div class="pywry-content">
<div class="pywry-scroll-container">
<div id="output">Ready</div>
</div>
</div>
</div>
</div>
</div>
<div class="pywry-toast-container pywry-toast-container--top-right"></div>
</body>
</html>
This exact same assembly runs for all rendering paths — native, notebook, and browser. The only difference is the transport layer used to deliver it.
Next Steps¶
- app.show() — The full
show()method and its parameters - HtmlContent — Controlling CSS, JS, and data injection
- Event System — How the JavaScript bridge communicates with Python
- Configuration — Global settings, asset paths, and security