diff --git a/docs/vitepress/.vitepress/config.mjs b/docs/vitepress/.vitepress/config.mjs
index 2e4f556a..9c22ca0c 100644
--- a/docs/vitepress/.vitepress/config.mjs
+++ b/docs/vitepress/.vitepress/config.mjs
@@ -81,11 +81,21 @@ export default defineConfig({
{ text: 'ParaView', link: '/guide/tutorial/paraview' },
]
},
+ {
+ text: 'Jupyter',
+ items: [
+ { text: 'Trame in Jupyter', link: '/guide/jupyter/intro' },
+ { text: 'Code example', link: '/guide/jupyter/sample-code' },
+ { text: 'How it works', link: '/guide/jupyter/how-it-works' },
+ { text: 'Advanced usecase', link: '/guide/jupyter/advanced' },
+ { text: 'Trame extension', link: '/guide/jupyter/extension' },
+ ]
+ },
{
text: 'Deployment',
items: [
{ text: 'Python CLI', link: '/guide/deployment/pypi' },
- { text: 'Jupyter', link: '/guide/deployment/jupyter' },
+ { text: 'Jupyter', link: '/guide/jupyter/intro' },
{ text: 'Desktop', link: '/guide/deployment/desktop' },
{ text: 'Cloud', link: '/guide/deployment/cloud' },
{ text: 'HPC / Clusters', link: '/guide/deployment/hpc' },
diff --git a/docs/vitepress/assets/images/deployment/tauri-cone-app.png b/docs/vitepress/assets/images/deployment/tauri-cone-app.png
new file mode 100644
index 00000000..0da7766d
Binary files /dev/null and b/docs/vitepress/assets/images/deployment/tauri-cone-app.png differ
diff --git a/docs/vitepress/assets/images/deployment/tauri.svg b/docs/vitepress/assets/images/deployment/tauri.svg
new file mode 100644
index 00000000..ab1a3185
--- /dev/null
+++ b/docs/vitepress/assets/images/deployment/tauri.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/vitepress/assets/images/jupyter/jupyter-cone.png b/docs/vitepress/assets/images/jupyter/jupyter-cone.png
new file mode 100644
index 00000000..2c6c2295
Binary files /dev/null and b/docs/vitepress/assets/images/jupyter/jupyter-cone.png differ
diff --git a/docs/vitepress/assets/images/jupyter/multi-ui.png b/docs/vitepress/assets/images/jupyter/multi-ui.png
new file mode 100644
index 00000000..6df155a2
Binary files /dev/null and b/docs/vitepress/assets/images/jupyter/multi-ui.png differ
diff --git a/docs/vitepress/guide/deployment/desktop.md b/docs/vitepress/guide/deployment/desktop.md
index b0bc977c..df020733 100644
--- a/docs/vitepress/guide/deployment/desktop.md
+++ b/docs/vitepress/guide/deployment/desktop.md
@@ -17,4 +17,11 @@ pip install \
python -m trame.app.demo --app
```
-![Simple trame app](/assets/images/deployment/cone-app.png)
\ No newline at end of file
+![Simple trame app](/assets/images/deployment/cone-app.png)
+
+![Tauri](/assets/images/deployment/tauri.svg)
+
+Otherwise you can also use tauri and fully leverage File Dialog, Multi-Windows, and App bundler for Linux, Mac and Windows.
+[Several examples are available](https://github.com/Kitware/trame-tauri/tree/master/examples) in its package repository.
+
+![Tauri trame app](/assets/images/deployment/tauri-cone-app.png)
\ No newline at end of file
diff --git a/docs/vitepress/guide/jupyter/advanced.md b/docs/vitepress/guide/jupyter/advanced.md
new file mode 100644
index 00000000..4d37a765
--- /dev/null
+++ b/docs/vitepress/guide/jupyter/advanced.md
@@ -0,0 +1,40 @@
+# Advanced usecase
+
+Now that you know we rely on an __``__ to serve the trame application, let's explore how you can tune various parameters to get the most of trame within Jupyter.
+
+## Network / iframe builder
+
+Jupyter provide various way to help network application to still work in their environment. You will often find a server proxy extension available, but we also created an extension for trame to better manage the network. But either way, it boils down to selecting an __iframe_builder__. In some case we try to make that selection for you automatically, but sometime you may want to override the default behavior.
+
+Let's review the various available options
+
+```python
+from module import App
+
+app = App()
+await app.ui.ready
+app.ui.iframe_builder = "..." # <= force an iframe builder
+app.ui
+```
+
+| iframe_builder | URL | Default if |
+| --- | --- | --- |
+| default | {iframe_base_url}:{server.port}/ | Default when nothing is set |
+| serverproxy | {iframe_base_url}/{server.port}/ | - |
+| jupyter-extension | ENV(TRAME_JUPYTER_WWW)/servers/{server.name}/ | Extension loaded and available |
+| jupyter-hub | ENV(JUPYTERHUB_SERVICE_PREFIX)proxy/{server.port}/ | If JUPYTERHUB_SERVICE_PREFIX exist |
+| jupyter-hub-host | ENV(JUPYTERHUB_SERVICE_PREFIX)proxy/{host}:{server.port}/ | Never a default |
+
+The selection can also be done via the __TRAME_IFRAME_BUILDER__ environment variable.
+
+On top of those presets, you can directly set a function as the iframe_builder. Below is
+
+## Configurable properties
+
+On top of the __iframe_builder__, the following set of attributes can be set by the user.
+
+| Property name | Default value | Layout constructor |
+| --- | --- | --- |
+| iframe_style | `border: none; width: 100%; height: 600px;` | `width="100%", height="600px",`
+| iframe_attrs | `{}` | - |
+| iframe_base_url | `http://localhost` | `base_url="http://localhost"` |
\ No newline at end of file
diff --git a/docs/vitepress/guide/jupyter/extension.md b/docs/vitepress/guide/jupyter/extension.md
new file mode 100644
index 00000000..23dc064b
--- /dev/null
+++ b/docs/vitepress/guide/jupyter/extension.md
@@ -0,0 +1,33 @@
+# trame-jupyter-extension
+
+The trame-jupyter-extension as opposed to server proxy will use the existing WebSocket connection to exchange data between the kernel and the client and serve the HTML content from the Jupyter web server.
+
+For __Jupyter Lab v3__, you must use the __"trame-jupyter-extension<2"__, while Jupyter Lab v4 will work with the latest version of the extension.
+
+You can install the extension by running `pip install trame-jupyter-extension` or via the __conda-forge__ channel.
+
+## Debugging checklist
+
+While the extension should just work, sometime things are not easy. This section, focus on the various steps needed to ensure that everything is a expected.
+
+1. The extension must be installed on the Jupyter Lab Server (not the kernel).
+ - __Test__: In the browser, open the "Dev tools", and check if `trameJupyter` is available in the console.
+ - __TroubleShoot__
+ - Extension not available in python `=> pip install correct version`
+ - Extension not enabled in the Web UI
+1. Make sure the extension properly configured the Kernel
+ - __Test__: In a Python cell the environment variable should not be empty
+ ```python
+ import os
+ print(os.environ.get("TRAME_JUPYTER_WWW"))
+ ```
+ - __TroubleShoot__
+ - Plugin failed to activate `=> ??`
+1. Make sure the iframe_builder use the extension resolver
+ - __Test__: In a Python cell run the following command and inspect the URL
+ ```python
+ app.ui._jupyter_content()
+ ```
+ - __TroubleShoot__
+ - Plugin failed to activate `=> ??`
+ - Someone override the iframe_builder?
diff --git a/docs/vitepress/guide/jupyter/how-it-works.md b/docs/vitepress/guide/jupyter/how-it-works.md
new file mode 100644
index 00000000..fd49903d
--- /dev/null
+++ b/docs/vitepress/guide/jupyter/how-it-works.md
@@ -0,0 +1,32 @@
+# How does it work?
+
+You might be wondering what more is needed as things are pretty simple so far. Well you may relize that while everything is working fine locally, when deployed in the cloud, it does not work anymore and you don't know why. This guide aims to cover how things works under the cover and what you can tune to make it work in your environment.
+Let's review the basic flow.
+
+```python
+from module import App # Get the application class
+
+app = App() # a) Instantiate application (with default server)
+await app.ui.ready # b) Wait for the UI to be ready
+app.ui # c) Display UI in cell output
+```
+
+## Application instantiation
+
+In the __(a)__ step, we instantiate the application but more importantly we lookup a trame server based on the constructor argument.
+When not provided, we get the default server. So instantiating the application twice will not lead to any isolation between them as they will reflect the exact same server state.
+
+## Waiting for the User Interface
+
+In the __(b)__ step, we asynchronously wait for something before executing the following lines...
+But technically, we are waiting for the associated server to run as a task and be ready to accept connections.
+This means that instead of running the server as the main process, we start it as a background task in the current asynchronous runtime.
+
+## Display the User Interface
+
+In the __(c)__ step, we simply return the layout object which Jupyter will use its `_jupyter_content_` method to display it in the cell. But technically we are building an `` pointing to our webserver (host:port) that is running as a task inside the Jupyter Kernel.
+
+As you can imagine, when deployed in the cloud, such default behavior won't work due to missing network route. But several solutions are available and covered in the next guide.
+
+
+
diff --git a/docs/vitepress/guide/jupyter/intro.md b/docs/vitepress/guide/jupyter/intro.md
new file mode 100644
index 00000000..2f5430b2
--- /dev/null
+++ b/docs/vitepress/guide/jupyter/intro.md
@@ -0,0 +1,50 @@
+# Trame and Jupyter Lab
+
+A trame application, while working in standlone fashion, can also be imported inside Jupyter and displayed within a notebook.
+To make that possible, the user will need to be able to import and instantiate such application in a Jupyter context. Then, the user will need to have access to the layout (ui) of that application so it can be displayed in the notebook flow.
+
+## Simple example
+
+If you want to give it a try, you can setup a virtual environment like below:
+
+```bash
+# Create virtual-environment
+python3 -m venv .venv
+
+# Activate environment
+source .venv/bin/activate # => Linux / Mac
+# .\.venv\Scripts\activate # => Window
+
+# Install dependencies
+pip install trame trame-vtk trame-vuetify # adding vuetify + vtk.js for demo app
+pip install setuptools # used in trame-vuetify but not always available nowaday
+pip install jupyterlab
+```
+
+Then you can start Jupyter Lab and run the follow cells
+
+```bash
+# start Jupyter
+jupyter lab
+```
+
+Then within a new notebook, you can import our trame cone demo example (We'll look into the code later).
+
+```python
+from trame.app.demo import Cone
+
+# Create new application instance
+app = Cone()
+
+# Let's start the server by waiting for its UI to be ready
+await app.ui.ready
+
+# Put the UI into the resulting cell
+app.ui
+```
+
+This should look like
+
+![Cone in Jupyter](/assets/images/jupyter/jupyter-cone.png)
+
+If you want [more examples using the same code, you can look at that binder example repository](https://github.com/Kitware/trame-binder).
\ No newline at end of file
diff --git a/docs/vitepress/guide/jupyter/sample-code.md b/docs/vitepress/guide/jupyter/sample-code.md
new file mode 100644
index 00000000..c9a79a2b
--- /dev/null
+++ b/docs/vitepress/guide/jupyter/sample-code.md
@@ -0,0 +1,89 @@
+# Sample code
+
+In the introduction we just imported an existing example application, but let's look at its internal and explain it.
+
+::: code-group
+<<< @/../../trame/app/demo.py
+:::
+
+## Key take away
+
+When a trame application is laid out like above, you can just run the following inside a cell to access it.
+
+```python
+from module import App
+
+app = App()
+await app.ui.ready
+app.ui
+```
+
+And in case you want to have a second instance independent of the first one, you can do
+
+```python
+app2 = App('v2')
+await app2.ui.ready
+app2.ui
+```
+
+## Changing application state
+
+In trame you simply can change a state variable to see the change reflected in the UI. But when doing that in another cell, it does not seems to work.
+The reason is the same as when you update a state variable from an asynchronous task, you need to be explicit when the state synchronization needs to happen. A simpler way to do that is to use the state as context manager like below.
+
+```python
+with app.state:
+ app.state.resolution = 24
+```
+
+That is why in our setter we added such logic which allow us to simply call from anywhere
+
+```python
+app.resolution = 32
+```
+
+## Create several small UI
+
+```python
+from trame.app import get_server
+from trame.widgets import html
+from trame.ui.html import DivLayout
+
+server = get_server()
+state = server.state
+
+def reset_slider():
+ state.slider_a = 2
+
+@state.change("slider_a")
+def udpate_result(slider_a, **_):
+ state.result = slider_a / 2
+
+with DivLayout(server, 'a', height=30) as ui_a:
+ html.Input(
+ type="range",
+ min=-1,
+ max=50,
+ step=0.1,
+ v_model_number=("slider_a", 2),
+ style="width: 100%;",
+ )
+
+await ui_a.ready
+ui_a
+```
+
+Then you can create more ui on the same server
+
+```python
+with DivLayout(server, 'b', height=30) as ui_b:
+ html.Button(
+ "Reset Value",
+ click=reset_slider,
+ )
+ html.Span("{{ slider_a }} / 2 = {{ result }}", style="margin-left: 2rem")
+
+ui_b
+```
+
+![Multi-UI in Jupyter](/assets/images/jupyter/multi-ui.png)