- Join in with the conversation on our
+
Join the conversation on our
discord server,
- for realtime chat with core maintainers and fellow users of PyScript.
- Find out more about the open source project, who is involved and how
- you could contribute or support our efforts via the
- contributor's guide.
- Explore
+ for realtime chat with core maintainers and fellow users of PyScript.
+ Check out our YouTube
+ channel, full of community calls and show and tells.
+ Explore
educational
and
commercial
diff --git a/docs/mini-coi.js b/docs/mini-coi.js
new file mode 100644
index 0000000..b7a23bf
--- /dev/null
+++ b/docs/mini-coi.js
@@ -0,0 +1,28 @@
+/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
+/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
+(({ document: d, navigator: { serviceWorker: s } }) => {
+ if (d) {
+ const { currentScript: c } = d;
+ s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
+ r.addEventListener('updatefound', () => location.reload());
+ if (r.active && !s.controller) location.reload();
+ });
+ }
+ else {
+ addEventListener('install', () => skipWaiting());
+ addEventListener('activate', e => e.waitUntil(clients.claim()));
+ addEventListener('fetch', e => {
+ const { request: r } = e;
+ if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
+ e.respondWith(fetch(r).then(r => {
+ const { body, status, statusText } = r;
+ if (!status || status > 399) return r;
+ const h = new Headers(r.headers);
+ h.set('Cross-Origin-Opener-Policy', 'same-origin');
+ h.set('Cross-Origin-Embedder-Policy', 'require-corp');
+ h.set('Cross-Origin-Resource-Policy', 'cross-origin');
+ return new Response(body, { status, statusText, headers: h });
+ }));
+ });
+ }
+})(self);
diff --git a/docs/user-guide/architecture.md b/docs/user-guide/architecture.md
new file mode 100644
index 0000000..be311b6
--- /dev/null
+++ b/docs/user-guide/architecture.md
@@ -0,0 +1,301 @@
+# Architecture, Lifecycle & Interpreters
+
+## Core concepts
+
+PyScript's architecture has three core concepts:
+
+1. A small, efficient and powerful kernel called
+ [PolyScript](https://github.com/pyscript/polyscript) is the foundation
+ upon which PyScript and plugins are built.
+2. A library called [coincident](https://github.com/WebReflection/coincident#readme)
+ that simplifies and coordinates interactions with web workers.
+3. The PyScript [stack](https://en.wikipedia.org/wiki/Solution_stack) inside
+ the browser is simple and clearly defined.
+
+### PolyScript
+
+[PolyScript](https://github.com/pyscript/polyscript) is the core of PyScript.
+
+!!! danger
+
+ Unless you are an advanced user, you only need to know that PolyScript
+ exists, and it can be safely ignored.
+
+PolyScript's purpose is to bootstrap the platform and provide all the necessary
+core capabilities. Setting aside PyScript for a moment, to use
+*just PolyScript* requires a `
+
+
+
+
+
+
+```
+
+!!! warning
+
+ **PolyScript is not PyScript.**
+
+ PyScript enhances the available Python interpreters with convenient
+ features, helper functions and easy-to-use yet powerful capabilities.
+
+ These enhancements are missing from PolyScript.
+
+PolyScript's capabilities, upon which PyScript is built, can be summarised as:
+
+* Evaluation of code via [`
+```
+
+If you use JSON, you can make it the value of the `config` attribute:
+
+```HTML title="JSON as the value of the config attribute."
+
+```
+
+For historical and convenience reasons we still support the inline
+specification of configuration information for `py` and `mpy` type scripts via a
+_single_ `` or `` tag in your HTML document:
+
+```HTML title="Inline configuration via the <py-config> tag."
+
+{
+ "packages": ["arrr", "numberwang" ]
+}
+
+```
+
+!!! warning
+
+ Should you use `` or ``, **there must be only one of
+ these tags on the page per interpreter**.
+
+ Additionally, `` only works for `py`/`mpy` type scripts and is not used
+ with [`py-game`](../pygame-ce) or [`py-editor`](../editor). For these use the config
+ attribute method.
+
+## Options
+
+There are five core options ([`interpreter`](#interpreter), [`files`](#files),
+[`packages`](#packages), [`js_modules`](#javascript-modules) and
+[`sync_main_only`](#sync_main_only)) and an experimental flag
+([`experimental_create_proxy`](#experimental_create_proxy)) that can be used in
+the configuration of PyScript. The user is also free to define
+arbitrary additional configuration options that plugins or an app may require
+for their own reasons.
+
+### Interpreter
+
+The `interpreter` option pins the Python interpreter to the version of the
+specified value. This is useful for testing (does my code work on a specific
+version of Pyodide?), or to ensure the precise combination of PyScript version
+and interpreter version are pinned to known values.
+
+The value of the `interpreter` option should be a valid version number
+for the Python interpreter you are configuring, or a fully qualified URL to
+a custom version of the interpreter.
+
+The following two examples are equivalent:
+
+```TOML title="Specify the interpreter version in TOML."
+interpreter = "0.23.4"
+```
+
+```JSON title="Specify the interpreter version in JSON."
+{
+ "interpreter": "0.23.4"
+}
+```
+
+The following JSON fragment uses a fully qualified URL to point to the same
+version of Pyodide as specified in the previous examples:
+
+```JSON title="Specify the interpreter via a fully qualified URL."
+{
+ "interpreter": "https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.mjs"
+}
+```
+
+### Files
+
+The `files` option fetches arbitrary content from URLs onto the virtual
+filesystem available to Python, and emulated by the browser. Just map a valid
+URL to a destination filesystem path on the in-browser virtual filesystem. You
+can find out more in the section about
+[PyScript and filesystems](../filesystem/).
+
+The following JSON and TOML are equivalent:
+
+```json title="Fetch files onto the filesystem with JSON."
+{
+ "files": {
+ "https://example.com/data.csv": "./data.csv",
+ "./code.py": "./subdir/code.py"
+ }
+}
+```
+
+```toml title="Fetch files onto the filesystem with TOML."
+[files]
+"https://example.com/data.csv" = "./data.csv"
+"./code.py" = "./subdir/code.py"
+```
+
+If you make the target an empty string, the final "filename" part of the source
+URL becomes the destination filename, in the root of the filesystem, to which
+the content is copied. As a result, the `data.csv` entry from the previous
+examples could be equivalently re-written as:
+
+```json title="JSON implied filename in the root directory."
+{
+ "files": {
+ "https://example.com/data.csv": "",
+ "./code.py": ""
+ }
+}
+```
+
+```toml title="TOML implied filename in the root directory."
+[files]
+"https://example.com/data.csv" = ""
+"./code.py" = ""
+```
+
+If the source part of the configuration is either a `.zip` or `.tar.gz` file
+and its destination is a folder path followed by a star (e.g. `/*` or
+`./dest/*`), then PyScript will extract the referenced archive automatically
+into the target directory in the browser's built in file system.
+
+!!! warning
+
+ **PyScript expects all file destinations to be unique.**
+
+ If there is a duplication PyScript will raise an exception to help you find
+ the problem.
+
+!!! warning
+ **Use destination URLs instead of CORS / redirect URLs.**
+
+ For example, `https://github.com/pyscript/ltk/raw/refs/heads/main/ltk/jquery.py`
+ redirects to `https://raw.githubusercontent.com/pyscript/ltk/refs/heads/main/ltk/jquery.py`. Use the latter.
+
+!!! tip
+
+ **For most people, most of the time, the simple URL to filename mapping,
+ described above, will be sufficient.**
+
+ Yet certain situations may require more flexibility. In which case, read
+ on.
+
+Sometimes many resources are needed to be fetched from a single location and
+copied into the same directory on the file system. To aid readability and
+reduce repetition, the `files` option comes with a mini
+[templating language](https://en.wikipedia.org/wiki/Template_processor)
+that allows reusable placeholders to be defined between curly brackets (`{`
+and `}`). When these placeholders are encountered in the `files` configuration,
+their name is replaced with their associated value.
+
+!!! Attention
+
+ Valid placeholder names are always enclosed between curly brackets
+ (`{` and `}`), like this: `{FROM}`, `{TO}` and `{DATA SOURCE}`
+ (capitalized names help identify placeholders
+ when reading code ~ although this isn't strictly necessary).
+
+ Any number of placeholders can be defined and used anywhere within URLs and
+ paths that map source to destination.
+
+The following JSON and TOML are equivalent:
+
+```json title="Using the template language in JSON."
+{
+ "files": {
+ "{DOMAIN}": "https://my-server.com",
+ "{PATH}": "a/path",
+ "{VERSION}": "1.2.3",
+ "{FROM}": "{DOMAIN}/{PATH}/{VERSION}",
+ "{TO}": "./my_module",
+ "{FROM}/__init__.py": "{TO}/__init__.py",
+ "{FROM}/foo.py": "{TO}/foo.py",
+ "{FROM}/bar.py": "{TO}/bar.py",
+ "{FROM}/baz.py": "{TO}/baz.py",
+ }
+}
+```
+
+```toml title="Using the template language in TOML."
+[files]
+"{DOMAIN}" = "https://my-server.com"
+"{PATH}" = "a/path"
+"{VERSION}" = "1.2.3"
+"{FROM}" = "{DOMAIN}/{PATH}/{VERSION}"
+"{TO}" = "./my_module"
+"{FROM}/__init__.py" = "{TO}/__init__.py"
+"{FROM}/foo.py" = "{TO}/foo.py"
+"{FROM}/bar.py" = "{TO}/bar.py"
+"{FROM}/baz.py" = "{TO}/baz.py"
+```
+
+The `{DOMAIN}`, `{PATH}`, and `{VERSION}` placeholders are
+used to create a further `{FROM}` placeholder. The `{TO}` placeholder is also
+defined to point to a common sub-directory on the file system. The final four
+entries use `{FROM}` and `{TO}` to copy over four files (`__init__.py`,
+`foo.py`, `bar.py` and `baz.py`) from the same source to a common destination
+directory.
+
+For convenience, if the destination is just a directory (it ends with `/`)
+then PyScript automatically uses the filename part of the source URL as the
+filename in the destination directory.
+
+For example, the end of the previous config file could be:
+
+```toml
+"{TO}" = "./my_module/"
+"{FROM}/__init__.py" = "{TO}"
+"{FROM}/foo.py" = "{TO}"
+"{FROM}/bar.py" = "{TO}"
+"{FROM}/baz.py" = "{TO}"
+```
+
+### Packages
+
+The `packages` option lists
+[Python packages](https://packaging.python.org/en/latest/)
+to be installed onto the Python path.
+
+!!! info
+
+ Pyodide uses a
+ [utility called `micropip`](https://micropip.pyodide.org/en/stable/index.html)
+ to install packages [from PyPI](https://pypi.org/).
+
+ Because `micropip` is a Pyodide-only feature, and MicroPython doesn't
+ support code packaged on PyPI, **the `packages` option only works with
+ packages hosted on PyPI when using Pyodide**.
+
+ MicroPython's equivalent utility,
+ [`mip`](https://docs.micropython.org/en/latest/reference/packages.html),
+ **uses a separate repository of available packages called
+ [`micropython-lib`](https://github.com/micropython/micropython-lib)**.
+ When you use the `packages` option with MicroPython, it is this repository
+ (not PyPI) that is used to find available packages. Many of the packages
+ in `micropython-lib` are for microcontroller based activities and
+ **may not work with the web assembly port** of MicroPython.
+
+ If you need **pure Python modules for MicroPython**, you have two further
+ options:
+
+ 1. Use the [files](#files) option to manually copy the source code for a
+ package onto the file system.
+ 2. Use a URL referencing a MicroPython friendly package instead of PyPI
+ package name.
+
+The following two examples are equivalent:
+
+```TOML title="A packages list in TOML."
+packages = ["arrr", "numberwang", "snowballstemmer>=2.2.0" ]
+```
+
+```JSON title="A packages list in JSON."
+{
+ "packages": ["arrr", "numberwang", "snowballstemmer>=2.2.0" ]
+}
+```
+
+When using Pyodide, the names in the list of `packages` can be any of the
+following valid forms:
+
+* A name of a package on PyPI: `"snowballstemmer"`
+* A name for a package on PyPI with additional constraints:
+ `"snowballstemmer>=2.2.0"`
+* An arbitrary URL to a Python package: `"https://.../package.whl"`
+* A file copied onto the browser based file system: `"emfs://.../package.whl"`
+
+#### Package Cache
+
+For performance reasons, PyScript caches packages so that a delay resulting
+from downloading the packages is only noticable on first load - after which,
+PyScript will fall back on packages previously downloaded and held in the
+browser's local cache.
+
+The behaviour of caching can be configured via the `packages_cache` setting. If
+this setting is not used, PyScript will cache packages. Otherwise, override
+PyScript's behaviour by setting `packages_cache` to one of these two values:
+
+* `never` - PyScript will not cache packages.
+* `passthrough` - this only works with Pyodide (see [this wiki](https://deepwiki.com/cloudflare/pyodide/3-package-system)),
+ and will cause Pyodide to download packages in a parallel manner rather than
+ the default linear fashion. However, these packages will not be cached.
+
+### Plugins
+
+The `plugins` option allows user to either augment, or exclude, the list of
+plugins imported out of the box from *core* during bootstrap.
+
+While augmenting requires some knowledge about *core* internals, excluding
+some plugin might be desired to avoid such plugin behavior and, in edge cases,
+reduce the amount of network requests to bootstrap *PyScript*.
+
+It is possible to check the [list of plugins](https://github.com/pyscript/pyscript/blob/main/core/src/plugins.js)
+we offer by default, where each *key* is used as plugin name and could be also
+disabled using the `!pugin-name` convention, here an example:
+
+```TOML title="Specify plugins in TOML"
+plugins = ["custom_plugin", "!error"]
+```
+
+```JSON title="Specify plugins in JSON"
+{
+ "plugins": ["custom_plugin", "!error"]
+}
+```
+
+!!! info
+
+ The `"!error"` syntax is a way to turn off a plugin built into PyScript
+ that is enabled by default.
+
+ It is possible to turn off other plugins too using the very same
+ convention.
+
+!!! warning
+
+ Please note `plugins` are currently a *core* only feature. If you need any
+ extra functionality out of the box *files* or *js_modules* are the current
+ way to provide more features without needing to file a *PR* in *core*.
+
+ This means that the current `plugins` proposal is meant to disable our own
+ plugins but it has no usage to add 3rd party plugins right now.
+
+### JavaScript modules
+
+It's easy to import and use JavaScript modules in your Python code. This
+section of the docs examines the configuration needed to make this work. How
+to make use of JavaScript is dealt with
+[elsewhere](../dom/#working-with-javascript).
+
+We need to tell PyScript about the JavaScript modules you want to
+use. This is the purpose of the `js_modules` related configuration fields.
+
+There are two fields:
+
+* `js_modules.main` defines JavaScript modules loaded in the context of the
+ main thread of the browser. Helpfully, it is also possible to interact with
+ such modules **from the context of workers**. Sometimes such modules also
+ need CSS files to work, and these can also be specified.
+* `js_modules.worker` defines JavaScript modules loaded into the context of
+ the web worker. Such modules **must not expect** `document` or `window`
+ references (if this is the case, you must load them via `js_modules.main` and
+ use them from the worker). However, if the JavaScript module could work
+ without such references, then performance is better if defined on a worker.
+ Because CSS is meaningless in the context of a worker, it is not possible to
+ specify such files in a worker context.
+
+Once specified, your JavaScript modules will be available under the
+`pyscript.js_modules.*` namespace.
+
+To specify such modules, simply provide a list of source/module name pairs.
+
+For example, to use the excellent [Leaflet](https://leafletjs.com/) JavaScript
+module for creating interactive maps you'd add the following lines:
+
+```TOML title="JavaScript main thread modules defined in TOML."
+[js_modules.main]
+"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js" = "leaflet"
+"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css" = "leaflet" # CSS
+```
+
+```JSON title="JavaScript main thread modules defined in JSON."
+{
+ "js_modules": {
+ "main": {
+ "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js": "leaflet",
+ "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css": "leaflet"
+ }
+ }
+}
+```
+
+!!! info
+
+ Notice how the second line references the required CSS needed for the
+ JavaScript module to work correctly.
+
+ The CSS file **MUST** target the very same name as the JavaScript module to
+ which it is related.
+
+!!! warning
+
+ Since the Leaflet module expects to manipulate the DOM and have access to
+ `document` and `window` references, **it must only be added via the
+ `js_modules.main` setting** (as shown) and cannot be added in a worker
+ context.
+
+At this point Python code running on either the main thread or in a
+worker will have access to the JavaScript module like this:
+
+```python title="Making use of a JavaScript module from within Python."
+from pyscript.js_modules import leaflet as L
+
+map = L.map("map")
+
+# etc....
+```
+
+Some JavaScript modules (such as
+[html-escaper](https://www.npmjs.com/package/html-escaper)) don't require
+access to the DOM and, for efficiency reasons, can be included in the worker
+context:
+
+```TOML title="A JavaScript worker module defined in TOML."
+[js_modules.worker]
+"https://cdn.jsdelivr.net/npm/html-escaper" = "html_escaper"
+```
+
+```JSON title="A JavaScript worker module defined in JSON."
+{
+ "js_modules": {
+ "worker": {
+ "https://cdn.jsdelivr.net/npm/html-escaper": "html_escaper"
+ }
+ }
+}
+```
+
+However, `from pyscript.js_modules import html_escaper` would then only work
+within the context of Python code **running on a worker**.
+
+### sync_main_only
+
+Sometimes you just want to start an expensive computation on a web worker
+without the need for the worker to interact with the main thread. You're simply
+awaiting the result of a method exposed from a worker.
+
+This has the advantage of not requiring the use of `SharedArrayBuffer` and
+[associated CORS related header configuration](../workers/#http-headers).
+
+If the `sync_main_only` flag is set, then **interactions between the main thread
+and workers are limited to one way calls from the main thread to methods
+exposed by the workers**.
+
+```TOML title="Setting the sync_main_only flag in TOML."
+sync_main_only = true
+```
+
+```JSON title="Setting the sync_main_only flag in JSON."
+{
+ "sync_main_only": true
+}
+```
+
+If `sync_main_only` is set, the following caveats apply:
+
+* It is not possible to manipulate the DOM or do anything meaningful on the
+ main thread **from a worker**. This is because Atomics cannot guarantee
+ sync-like locks between a worker and the main thread.
+* Only a worker's `pyscript.sync` methods are exposed, and **they can only be
+ awaited from the main thread**.
+* The worker can only `await` main thread references one after the other, so
+ developer experience is degraded when one needs to interact with the
+ main thread.
+
+### experimental_create_proxy
+
+Knowing when to use the `pyscript.ffi.create_proxy` method when using Pyodide
+can be confusing at the best of times and full of
+[technical "magic"](../ffi#create_proxy).
+
+This _experimental_ flag, when set to `"auto"` will cause PyScript to try to
+automatically handle such situations, and should "just work".
+
+```TOML title="Using the experimental_create_proxy flag in TOML."
+experimental_create_proxy = "auto"
+```
+
+```JSON title="Using the experimental_create_proxy flag in JSON."
+{
+ "experimental_create_proxy": "auto"
+}
+```
+
+!!! warning
+
+ **This feature is _experimental_ and only needs to be used with Pyodide.**
+
+ Should you encounter problems (such as problematic memory leaks) when using
+ this flag with Pyodide, please don't hesitate to
+ [raise an issue](https://github.com/pyscript/pyscript/issues) with a
+ reproducable example, and we'll investigate.
+
+### experimental_ffi_timeout
+
+When bootstrapping a worker, the worker is told to use a cache for round-trip
+operations (for example, `window.my_object.foo.bar.baz` causes a round-trip to
+the main thread for each dot `.` in this chain of references). By caching the
+dotted references performance can be improved by reducing the number of
+round trips PyScript makes.
+
+However, not everything can be cached (those APIs or objects with side-effects
+won't work; for example DOM element based APIs etc).
+
+The `experimental_ffi_timeout` setting defines the maximum lifetime of that
+cache. If it's less than 0 (the default), there is no cache whatsoever. Zero
+means to clean up the cache on the next iteration of the event loop. A positive
+number is the maximum number of milliseconds the cache will be kept alive.
+
+In this experimental phase, we suggest trying `0`, `30` or a value that won't
+likely bypass the browser rendering of 60fps. Of course, `1000` (i.e. a second)
+would be a fun, if greedy, experiment.
+
+### debug
+
+When using Pyodide, if the `debug` setting is set to `true`, then Pyodide will
+run in debug mode. See Pyodide's documentation for details of what this
+entails.
+
+### Custom
+
+Sometimes plugins or apps need bespoke configuration options.
+
+So long as you don't cause a name collision with the built-in option names then
+you are free to use any valid data structure that works with both TOML and JSON
+to express your configuration needs.
+
+Access the current configuration via `pyscript.config`, a Python `dict`
+representing the configuration:
+
+```python title="Reading the current configuration."
+from pyscript import config
+
+
+# It's just a dict.
+print(config.get("files"))
+```
+
+!!! note
+
+ Changing the `config` dictionary at runtime doesn't change the actual
+ configuration.
diff --git a/docs/user-guide/dom.md b/docs/user-guide/dom.md
index dc2117a..1859cb8 100644
--- a/docs/user-guide/dom.md
+++ b/docs/user-guide/dom.md
@@ -1,2 +1,493 @@
-# Working with the DOM
+# The DOM
+The DOM
+([document object model](https://developer.mozilla.org/en-US/docs/Web/API/Document_object_model))
+is a tree like data structure representing the web page displayed by the
+browser. PyScript interacts with the DOM to change the user interface and react
+to things happening in the browser.
+
+There are currently two ways to interact with the DOM:
+
+1. Through the [foreign function interface](#ffi) (FFI) to interact with objects found
+ in the browser's `globalThis` or `document` objects.
+2. Through the [`pydom` module](#pydom) that acts as a Pythonic wrapper around
+ the FFI and comes as standard with PyScript.
+
+## FFI
+
+The foreign function interface (FFI) gives Python access to all the
+[standard web capabilities and features](https://developer.mozilla.org/en-US/docs/Web),
+such as the browser's built-in
+[web APIs](https://developer.mozilla.org/en-US/docs/Web/API).
+
+This is available via the `pyscript.window` module which is a proxy for
+the main thread's `globalThis` object, or `pyscript.document` which is a proxy
+for the website's `document` object in JavaScript:
+
+```Python title="Accessing the window and document objects in Python"
+from pyscript import window, document
+
+
+my_element = document.querySelector("#my-id")
+my_element.innerText = window.location.hostname
+```
+
+The FFI creates _proxy objects_ in Python linked to _actual objects_ in
+JavaScript.
+
+The proxy objects in your Python code look and behave like Python
+objects but have related JavaScript objects associated with them. It means the
+API defined in JavaScript remains the same in Python, so any
+[browser based JavaScript APIs](https://developer.mozilla.org/en-US/docs/Web/API)
+or third party JavaScript libraries that expose objects in the web page's
+`globalThis`, will have exactly the same API in Python as in JavaScript.
+
+The FFI automatically transforms Python and JavaScript objects into the
+equivalent in the other language. For example, Python's boolean `True` and
+`False` will become JavaScript's `true` and `false`, while a JavaScript array
+of strings and integers, `["hello", 1, 2, 3]` becomes a Python list of the
+equivalent values: `["hello", 1, 2, 3]`.
+
+!!! info
+
+ Instantiating classes into objects is an interesting special case that the
+ FFI expects you to handle.
+
+ **If you wish to instantiate a JavaScript class in your Python
+ code, you need to call the class's `new` method:**
+
+ ```python
+ from pyscript import window
+
+
+ my_obj = window.MyJavaScriptClass.new("some value")
+
+ ```
+
+ The underlying reason for this is simply JavaScript and Python do
+ instantiation very differently. By explicitly calling the JavaScript
+ class's `new` method PyScript both signals and honours this difference.
+
+ More technical information about instantiating JavaScript classes can be
+ [found in the FAQ](../../faq/#javascript-classnew)
+
+Should you require lower level API access to FFI features, you can find such
+builtin functions under the `pyscript.ffi` namespace in both Pyodide and
+MicroPython. The available functions are described in our section on the
+[builtin API](../../api).
+
+Advanced users may wish to explore the
+[technical details of the FFI](../ffi).
+
+## `pyscript.web`
+
+!!! warning
+
+ The `pyscript.web` module is currently a work in progress.
+
+ We welcome feedback and suggestions.
+
+The `pyscript.web` module is an idiomatically Pythonic API for interacting with
+the DOM. It wraps the FFI in a way that is more familiar to Python developers
+and works natively with the Python language. Technical documentation for this
+module can be found in [the API](../../api/#pyscriptweb) section.
+
+There are three core concepts to remember:
+
+* Find elements on the page via
+ [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors).
+ The `find` API uses exactly the [same queries](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Locating_DOM_elements_using_selectors)
+ as those used by native browser methods like `qurerySelector` or
+ `querySelectorAll`.
+* Use classes in the `pyscript.web` namespace to create and organise
+ new elements on the web page.
+* Collections of elements allow you to access and change attributes en-mass.
+ Such collections are returned from `find` queries and are also used for the
+ [children](https://developer.mozilla.org/en-US/docs/Web/API/Element/children)
+ of an element.
+
+You have several options for accessing the content of the page, and these are
+all found in the `pyscript.web.page` object. The `html`, `head` and `body`
+attributes reference the page's top-level html, head and body. As a convenience
+the `page`'s `title` attribute can be used to get and set the web page's title
+(usually shown in the browser's tab). The `append` method is a shortcut for
+adding content to the page's `body`. Whereas, the `find` method is used to
+return collections of elements matching a CSS query. You may also shortcut
+`find` via a CSS query in square brackets. Finally, all elements have a `find`
+method that searches within their children for elements matching your CSS
+query.
+
+```python
+from pyscript.web import page
+
+
+# Print all the child elements of the document's head.
+print(page.head.children)
+# Find all the paragraphs in the DOM.
+paragraphs = page.find("p")
+# Or use square brackets.
+paragraphs = page["p"]
+```
+
+The object returned from a query, or used as a reference to an element's
+children is iterable:
+
+```python
+from pyscript.web import page
+
+
+# Get all the paragraphs in the DOM.
+paragraphs = page["p"]
+
+# Print the inner html of each paragraph.
+for p in paragraphs:
+ print(p.html)
+```
+
+Alternatively, it is also indexable / sliceable:
+
+```python
+from pyscript.web import page
+
+
+# Get an ElementCollection of all the paragraphs in the DOM
+paragraphs = page["p"]
+
+# Only the final two paragraphs.
+for p in paragraphs[-2:]:
+ print(p.html)
+```
+
+You have access to all the standard attributes related to HTML elements (for
+example, the `innerHTML` or `value`), along with a couple of convenient ways
+to interact with classes and CSS styles:
+
+* `classes` - the list of classes associated with the elements.
+* `style` - a dictionary like object for interacting with CSS style rules.
+
+For example, to continue the example above, `paragraphs.innerHTML` will return
+a list of all the values of the `innerHTML` attribute on each contained
+element. Alternatively, set an attribute for all elements contained in the
+collection like this: `paragraphs.style["background-color"] = "blue"`.
+
+It's possible to create new elements to add to the page:
+
+```python
+from pyscript.web import page, div, select, option, button, span, br
+
+
+page.append(
+ div(
+ div("Hello!", classes="a-css-class", id="hello"),
+ select(
+ option("apple", value=1),
+ option("pear", value=2),
+ option("orange", value=3),
+ ),
+ div(
+ button(span("Hello! "), span("World!"), id="my-button"),
+ br(),
+ button("Click me!"),
+ classes=["css-class1", "css-class2"],
+ style={"background-color": "red"}
+ ),
+ div(
+ children=[
+ button(
+ children=[
+ span("Hello! "),
+ span("Again!")
+ ],
+ id="another-button"
+ ),
+ br(),
+ button("b"),
+ ],
+ classes=["css-class1", "css-class2"]
+ )
+ )
+)
+```
+
+This example demonstrates a declaritive way to add elements to the body of the
+page. Notice how the first (unnamed) arguments to an element are its children.
+The named arguments (such as `id`, `classes` and `style`) refer to attributes
+of the underlying HTML element. If you'd rather be explicit about the children
+of an element, you can always pass in a list of such elements as the named
+`children` argument (you see this in the final `div` in the example above).
+
+Of course, you can achieve similar results in an imperative style of
+programming:
+
+```python
+from pyscript.web import page, div, p
+
+
+my_div = div()
+my_div.style["background-color"] = "red"
+my_div.classes.add("a-css-class")
+
+my_p = p()
+my_p.content = "This is a paragraph."
+
+my_div.append(my_p)
+
+# etc...
+```
+
+It's also important to note that the `pyscript.when` decorator understands
+element references from `pyscript.web`:
+
+```python
+from pyscript import when
+from pyscript.web import page
+
+
+btn = page["#my-button"]
+
+
+@when("click", btn)
+def my_button_click_handler(event):
+ print("The button has been clicked!")
+```
+
+Should you wish direct access to the proxy object representing the underlying
+HTML element, each Python element has a `_dom_element` property for this
+purpose.
+
+Once again, the technical details of these classes are described in the
+[built-in API documentation](../../api/#pyscriptweb).
+
+## Working with JavaScript
+
+There are three ways in which JavaScript can get into a web page.
+
+1. As a global reference attached to the `window` object in the web page
+ because the code was referenced as the source of a `script` tag in your HTML
+ (the very old school way to do this).
+2. Using the [Universal Module Definition](https://github.com/umdjs/umd) (UMD),
+ an out-of-date and non-standard way to create JavaScript modules.
+3. As a standard
+ [JavaScript Module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
+ which is the modern, standards compliant way to define and use a JavaScript
+ module. If possible, this is the way you should do things.
+
+Sadly, this being the messy world of the web, methods 1 and 2 are still quite
+common, and so you need to know about them so you're able to discern and work
+around them. There's nothing WE can do about this situation, but we can
+suggest "best practice" ways to work around each situation.
+
+Remember, as mentioned
+[elsewhere in our documentation](../configuration/#javascript-modules),
+the standard way to get JavaScript modules into your PyScript Python context is
+to link a _source_ standard JavaScript module to a _destination_ name:
+
+```toml title="Reference a JavaScript module in the configuration."
+[js_modules.main]
+"https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js" = "leaflet"
+```
+
+Then, reference the module via the destination name in your Python code, by
+importing it from the `pyscript.js_modules` namespace:
+
+```python title="Import the JavaScript module into Python"
+from pyscript.js_modules import leaflet as L
+
+map = L.map("map")
+
+# etc....
+```
+
+We'll deal with each of the potential JavaScript related situations in turn:
+
+### JavaScript as a global reference
+
+In this situation, you have some JavaScript code that just globally defines
+"stuff" in the context of your web page via a `script` tag. Your HTML will
+contain something like this:
+
+```html title="JavaScript as a global reference"
+
+
+
+
+
+
+```
+
+When you find yourself in this situation, simply use the `window` object in
+your Python code (found in the `pyscript` namespace) to interact with the
+resulting JavaScript objects:
+
+```python title="Python interaction with the JavaScript global reference"
+from pyscript import window, document
+
+
+# The window object is the global context of your web page.
+html = window.html
+
+# Just use the object "as usual"...
+# e.g. show escaped HTML in the body: <>
+document.body.append(html.escape("<>"))
+```
+
+You can find an example of this technique here:
+
+[https://pyscript.com/@agiammarchi/floral-glade/v1](https://pyscript.com/@agiammarchi/floral-glade/v1)
+
+### JavaScript as a non-standard UMD module
+
+Sadly, these sorts of non-standard JavaScript modules are still quite
+prevalent. But the good news is there are strategies you can use to help you
+get them to work properly.
+
+The non-standard UMD approach tries to check for `export` and `module` fields
+in the JavaScript module and, if it doesn’t find them, falls back to treating
+the module in the same way as a global reference described above.
+
+If you find you have a UMD JavaScript module, there are services online to
+automagically convert it to the modern and standards compliant way to d
+o JavaScript modules. A common (and usually reliable) service is provided by
+[https://esm.run/your-module-name](https://esm.run/your-module-name), a
+service that provides an out of the box way to consume the module in the
+correct and standard manner:
+
+```html title="Use esm.run to automatically convert a non-standard UMD module"
+
+
+```
+
+If a similar test works for the module you want to use, use the esm.run CDN
+service within the `py` or `mpy` configuration file as explained at the start
+of this section on JavaScript (i.e. you'll use it via the `pyscript.js_modules`
+namespace).
+
+If this doesn't work, assume the module is not updated nor migrated to a state
+that can be automatically translated by services like esm.run. You could try an
+alternative (more modern) JavaScript module to achieve you ends or (if it
+really must be this module), you can wrap it in a new JavaScript module that
+conforms to the modern standards.
+
+The following four files demonstrate this approach:
+
+```html title="index.html - still grab the script so it appears as a global reference."
+
+...
+
+
+...
+```
+
+```js title="wrapper.js - this grabs the JavaScript functionality from the global context and wraps it (exports it) in the modern standards compliant manner."
+// get all utilities needed from the global.
+const { escape, unescape } = globalThis.html;
+
+// export utilities like a standards compliant module would do.
+export { escape, unescape };
+```
+
+```toml title="pyscript.toml - configure your JS modules as before, but use your wrapper instead of the original module."
+[js_modules.main]
+# will simulate a standard JS module
+"./wrapper.js" = "html_escaper"
+```
+
+```python title="main.py - just import the module as usual and make use of it."
+from pyscript import document
+
+# import the module either via
+from pyscript.js_modules import html_escaper
+# or via
+from pyscript.js_modules.html_escaper import escape, unescape
+
+# show on body: <>
+document.body.append(html.escape("<>"))
+```
+
+You can see this approach in action here:
+
+[https://pyscript.com/@agiammarchi/floral-glade/v2](https://pyscript.com/@agiammarchi/floral-glade/v2)
+
+### A standard JavaScript module
+
+This is both the easiest and best way to import any standard JS module into
+Python.
+
+You don't need to reference the script in your HTML, just define how the source
+JavaScript module maps into the `pyscript.js_modules` namespace in your
+configuration file, as explained above.
+
+That's it!
+
+Here is an example project that uses this approach:
+
+[https://pyscript.com/@agiammarchi/floral-glade/v3](https://pyscript.com/@agiammarchi/floral-glade/v3)
+
+
+### My own JavaScript code
+
+If you have your own JavaScript work, just remember to write it as a standard
+JavaScript module. Put simply, ensure you `export` the things you need to. For
+instance, in the following fragment of JavaScript, the two functions are
+exported from the module:
+
+```js title="code.js - containing two functions exported as capabilities of the module."
+/*
+Some simple JavaScript functions for example purposes.
+*/
+
+export function hello(name) {
+ return "Hello " + name;
+}
+
+export function fibonacci(n) {
+ if (n == 1) return 0;
+ if (n == 2) return 1;
+ return fibonacci(n - 1) + fibonacci(n - 2);
+}
+```
+
+Next, just reference this module in the usual way in your TOML or JSON
+configuration file:
+
+```TOML title="pyscript.toml - references the code.js module so it will appear as the code module in the pyscript.js_modules namespace."
+[js_modules.main]
+"code.js" = "code"
+```
+
+In your HTML, reference your Python script with this configuration file:
+
+```html title="Reference the expected configuration file."
+
+```
+
+Finally, just use your JavaScript module’s exported functions inside PyScript:
+
+```python title="Just call your bespoke JavaScript code from Python."
+from pyscript.js_modules import code
+
+
+# Just use the JS code from Python "as usual".
+greeting = code.hello("Chris")
+print(greeting)
+result = code.fibonacci(12)
+print(result)
+```
+
+You can see this in action in the following example project:
+
+[https://pyscript.com/@ntoll/howto-javascript/latest](https://pyscript.com/@ntoll/howto-javascript/latest)
diff --git a/docs/user-guide/editor.md b/docs/user-guide/editor.md
new file mode 100644
index 0000000..ba54f0e
--- /dev/null
+++ b/docs/user-guide/editor.md
@@ -0,0 +1,248 @@
+# Python editor
+
+The PyEditor is a core plugin.
+
+!!! warning
+
+ Work on the Python editor is in its early stages. We have made it available
+ in this version of PyScript to give the community an opportunity to play,
+ experiment and provide feedback.
+
+ Future versions of PyScript will include a more refined, robust and perhaps
+ differently behaving version of the Python editor.
+
+If you specify the type of a `
+
+```
+
+However, different editors can share the same interpreter if they share the
+same `env` attribute value.
+
+```html title="Two editors sharing the same MicroPython environment."
+
+
+```
+
+The outcome of these code fragments should look something like this:
+
+
+
+!!! info
+
+ Notice that the interpreter type, and optional environment name is shown
+ at the top right above the Python editor.
+
+ Hovering over the Python editor reveals the "run" button.
+
+### Stop evaluation
+
+Sometimes, for whatever reason, the fragment of code in the editor will never
+complete. Perhaps it's stuck in an infinite loop and you need to stop the
+evaluation of your code so you can fix the problem and start it again.
+
+When the code is running, hovering over the editor will reveal a stop button
+(where the run button was found). Click on it, confirm you want to stop your
+code, and then the code will stop and the editor will refresh so you can fix
+your code.
+
+It looks something like this:
+
+
+
+### Setup
+
+Sometimes you need to create a pre-baked Pythonic context for a shared
+environment used by an editor. This need is especially helpful in educational
+situations where boilerplate code can be run, with just the important salient
+code available in the editor.
+
+To achieve this end use the `setup` attribute within a `script` tag. The
+content of this editor will not be shown, but will bootstrap the referenced
+environment automatically before any following editor within the same
+environment is evaluated.
+
+```html title="Bootstrapping an environment with `setup`"
+
+
+
+```
+
+Finally, the `target` attribute allows you to specify a node into which the
+editor will be rendered:
+
+```html title="Specify a target for the Python editor."
+
+
+```
+
+## Editor VS Terminal
+
+The editor and terminal are commonly used to embed interactive Python code into
+a website. However, there are differences between the two plugins, of which you
+should be aware.
+
+The main difference is that a `py-editor` or `mpy-editor` is an isolated
+environment (from the rest of PyScript that may be running on the page) and
+its code always runs in a web worker. We do this to prevent accidental blocking
+of the main thread that would freeze your browser's user interface.
+
+Because an editor is isolated from regular *py* or *mpy* scripts, one should
+not expect the same behavior regular *PyScript* elements follow, most notably:
+
+ * The editor's user interface is based on
+ [CodeMirror](https://codemirror.net/) and not on XTerm.js
+ [as it is for the terminal](../terminal).
+ * Code is evaluated all at once and asynchronously when the *Run* button is
+ pressed (not each line at a time, as in the terminal).
+ * The editor has listeners for `Ctrl-Enter` or `Cmd-Enter`, and
+ `Shift-Enter` to shortcut the execution of all the code. These shortcuts
+ make no sense in the terminal as each line is evaluated separately.
+ * There is a clear separation between the code and any resulting output.
+ * You may not use blocking calls (like `input`) with the editor, whereas
+ these will work if running the terminal via a worker.
+ * It's an editor! So simple or complex programs can be fully written without
+ running the code until ready. In the terminal, code is evaluated one line
+ at a time as it is typed in.
+ * There is no special reference to the underlying editor instance, while
+ there is both `script.terminal` or `__terminal__` in the terminal.
+
+## Read / Write / Execute
+
+Sometimes you need to programatically read, write or execute code in an
+editor. Once PyScript has started, every py-editor/mpy-editor script tag gets
+a `code` accessor attached to it.
+
+```python
+from pyscript import document
+
+# Grab the editor script reference.
+editor = document.querySelector('#editor')
+
+# Output the live content of the editor.
+print(editor.code)
+
+# Update the live content of the editor.
+editor.code = """
+a = 1
+b = 2
+print(a + b)
+"""
+
+# Evaluate the live code in the editor.
+# This could be any arbitrary code to evaluate in the editor's Python context.
+editor.process(editor.code)
+```
+
+## Configuration
+
+Unlike `
+
+
+```
+
+This
+[live example](https://agiammarchi.pyscriptapps.com/pyeditor-iot-example/latest/)
+shows how the editor can be used to execute code via a USB serial connection to
+a connected MicroPython microcontroller.
+
+## Tab behavior
+
+We currently trap the `tab` key in a way that reflects what a regular code
+editor would do: the code is simply indented, rather than focus moving to
+another element.
+
+We are fully aware of the implications this might have around accessibility so
+we followed
+[this detailed advice from Codemirror's documentation](https://codemirror.net/examples/tab/)
+We have an *escape hatch* to move focus outside the editor. Press `esc` before
+`tab` to move focus to the next focusable element. Otherwise `tab` indents
+code.
+
+
+## Still missing
+
+The PyEditor is currently under active development and refinement, so features
+may change (depending on user feedback). For instance, there is currently no
+way to stop or kill a web worker that has got into difficulty from the editor
+(hint: refreshing the page will reset things).
diff --git a/docs/user-guide/features.md b/docs/user-guide/features.md
new file mode 100644
index 0000000..cf0f2d3
--- /dev/null
+++ b/docs/user-guide/features.md
@@ -0,0 +1,87 @@
+# Features
+
+
+ - All the web
+ -
+
Pyscript gives you full access to the DOM and all
+ the web
+ APIs implemented by your browser.
+
+ Thanks to the foreign
+ function interface (FFI), Python just works with all the browser has to
+ offer, including any third party JavaScript libraries that may be included
+ in the page.
+
+ The FFI is bi-directional ~ it also enables JavaScript to access the
+ power of Python.
+
+ - All of Python
+ -
+
PyScript brings you two Python interpreters:
+
+ - Pyodide - the original standard
+ CPython interpreter you know and love, but compiled to WebAssembly.
+
+ - MicroPython - a lean and
+ efficient reimplementation of Python3 that includes a comprehensive
+ subset of the standard library, compiled to WebAssembly.
+
+ Because it is just regular CPython, Pyodide puts Python's deep and
+ diverse ecosystem of libraries, frameworks
+ and modules at your disposal. No matter the area of computing endeavour,
+ there's probably a Python library to help. Got a favourite library in
+ Python? Now you can use it in the browser and share your work with just
+ a URL.
+ MicroPython, because of its small size (170k) and speed, is especially
+ suited to running on more constrained browsers, such as those on mobile
+ or tablet devices. It includes a powerful sub-set of the Python standard
+ library and efficiently exposes the expressiveness of Python to the
+ browser.
+ Both Python interpreters supported by PyScript implement the
+ same FFI to bridge the gap between the worlds of Python
+ and the browser.
+
+
+ - AI and Data science built in
+ - Python is famous for its extraordinary usefulness in artificial
+ intelligence and data science. The Pyodide interpreter comes with many of
+ the libraries you need for this sort of work already baked in.
+
+ - Mobile friendly MicroPython
+ -
+
Thanks to MicroPython in PyScript, there is a compelling story for
+ Python on mobile.
+
+ MicroPython is small and fast enough that your app will start quickly
+ on first load, and almost instantly (due to the cache) on subsequent
+ runs.
+
+ - Parallel execution
+ - Thanks to a browser technology called
+ web workers
+ expensive and blocking computation can run somewhere other than the main
+ application thread controlling the user interface. When such work is done
+ on the main thread, the browser appears frozen; web workers ensure
+ expensive blocking computation happens elsewhere.
+ Think of workers as independent subprocesses in your web page.
+
+ - Rich and powerful plugins
+ -
+
PyScript has a small, efficient yet powerful core called
+ PolyScript. Most of
+ the functionality of PyScript is actually implemented through PolyScript's
+ plugin system.
+
+ This approach ensures a clear separation of concerns: PolyScript
+ can focus on being small, efficient and powerful, whereas the PyScript
+ related plugins allow us to Pythonically build upon the solid foundations
+ of PolyScript.
+
+ Because there is a plugin system, folks
+ independent of the PyScript core team have a way to create and
+ contribute to a rich ecosystem of plugins whose functionality reflects the
+ unique and diverse needs of PyScript's users.
+
+
+
+
diff --git a/docs/user-guide/ffi.md b/docs/user-guide/ffi.md
index 60b0e58..a874599 100644
--- a/docs/user-guide/ffi.md
+++ b/docs/user-guide/ffi.md
@@ -1 +1,211 @@
-# Foreign Function Interface (FFI)
+# PyScript FFI
+
+The foreign function interface (FFI) gives Python access to JavaScript, and
+JavaScript access to Python. As a result PyScript is able to access all the
+standard APIs and capabilities provided by the browser.
+
+We provide a unified `pyscript.ffi` because
+[Pyodide's FFI](https://pyodide.org/en/stable/usage/api/python-api/ffi.html)
+is only partially implemented in MicroPython and there are some fundamental
+differences. The `pyscript.ffi` namespace smooths out such differences into
+a uniform and consistent API.
+
+Our `pyscript.ffi` offers the following utilities:
+
+* `ffi.to_js(reference)` converts a Python object into its JavaScript
+ counterpart.
+* `ffi.create_proxy(def_or_lambda)` proxies a generic Python function into a
+ JavaScript one, without destroying its reference right away.
+
+Should you require access to Pyodide or MicroPython's specific version of the
+FFI you'll find them under the `pyodide.ffi` and `micropython.ffi` namespaces.
+Please refer to the documentation for those projects for further information.
+
+## to_js
+
+In the
+[Pyodide project](https://pyodide.org/en/stable/usage/api/python-api/ffi.html#pyodide.ffi.to_js),
+this utility converts Python dictionaries into
+[JavaScript `Map` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
+Such `Map` objects reflect the `obj.get(field)` semantics native to Python's
+way of retrieving a value from a dictionary.
+
+Unfortunately, this default conversion breaks the vast majority of native and
+third party JavaScript APIs. This is because the convention in idiomatic
+JavaScript is to use an [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects)
+for such key/value data structures (not a `Map` instance).
+
+A common complaint has been the repeated need to call `to_js` with the long
+winded argument `dict_converter=js.Object.fromEntries`. It turns out, most
+people most of the time simply want to map a Python `dict` to a JavaScript
+`object` (not a `Map`).
+
+Furthermore, in MicroPython the default Python `dict` conversion is to the
+idiomatic and sensible JavaScript `object`, making the need to specify a
+dictionary converter pointless.
+
+Therefore, if there is no reason to hold a Python reference in a JavaScript
+context (which is 99% of the time, for common usage of PyScript) then use the
+`pyscript.ffi.to_js` function, on both Pyodide and MicroPython, to always
+convert a Python `dict` to a JavaScript `object`.
+
+```html title="to_js: pyodide.ffi VS pyscript.ffi"
+
+
+
+
+
+```
+
+!!! Note
+
+ It is still possible to specify a different `dict_converter` or use Pyodide
+ specific features while converting Python references by simply overriding
+ the explicit field for `dict_converter`.
+
+ However, we cannot guarantee all fields and features provided by Pyodide
+ will work in the same way on MicroPython.
+
+## create_proxy
+
+In the
+[Pyodide project](https://pyodide.org/en/stable/usage/api/python-api/ffi.html#pyodide.ffi.create_proxy),
+this function ensures that a Python callable associated with an event listener,
+won't be garbage collected immediately after the function is assigned to the
+event. Therefore, in Pyodide, if you do not wrap your Python function, it is
+immediately garbage collected after being associated with an event listener.
+
+This is so common a gotcha (see the FAQ for
+[more on this](../../faq#borrowed-proxy)) that the Pyodide project have already
+created many work-arounds to address this situation. For example, the
+`create_once_callable`, `pyodide.ffi.wrappers.add_event_listener` and
+`pyodide.ffi.set_timeout` are all methods whose goal is to automatically manage
+the lifetime of the passed in Python callback.
+
+Add to this situation methods connected to the JavaScript `Promise` object
+(`.then` and `.catch` callbacks that are implicitly handled to guarantee no
+leaks once executed) and things start to get confusing and overwhelming with
+many ways to achieve a common end result.
+
+Ultimately, user feedback suggests folks simply want to do something like this,
+as they write their Python code:
+
+```python title="Define a callback without create_proxy."
+import js
+from pyscript import window
+
+
+def callback(msg):
+ """
+ A Python callable that logs a message.
+ """
+ window.console.log(msg)
+
+
+# Use the callback without having to explicitly create_proxy.
+js.setTimeout(callback, 1000, 'success')
+```
+
+Therefore, PyScript provides an experimental configuration flag called
+`experimental_create_proxy = "auto"`. When set, you should never have to care
+about these technical details nor use the `create_proxy` method and all the
+JavaScript callback APIs should just work.
+
+Under the hood, the flag is strictly coupled with the JavaScript garbage
+collector that will eventually destroy all proxy objects created via the
+[FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry)
+built into the browser.
+
+This flag also won't affect MicroPython because it rarely needs a
+`create_proxy` at all when Python functions are passed to JavaScript event
+handlers. MicroPython automatically handles this situation. However,
+there might still be rare and niche cases in MicroPython where such a
+conversion might be needed.
+
+Hence, PyScript retains the `create_proxy` method, even though it does not
+change much in the MicroPython world, although it might be still needed with
+the Pyodide runtime is you don't use the `experimental_create_proxy = "auto"`
+flag.
+
+At a more fundamental level, MicroPython doesn't provide (yet) a way to
+explicitly destroy a proxy reference, whereas Pyodide still expects to
+explicitly invoke `proxy.destroy()` when the function is not needed.
+
+!!! warning
+
+ In MicroPython proxies might leak due to the lack of a `destroy()` method.
+
+ Happily, proxies are usually created explicitly for event listeners or
+ other utilities that won't need to be destroyed in the future. So the lack
+ of a `destroy()` method in MicroPython is not a problem in this specific,
+ and most common, situation.
+
+ Until we have a `destroy()` in MicroPython, we suggest testing the
+ `experimental_create_proxy` flag with Pyodide so both runtimes handle
+ possible leaks automatically.
+
+For completeness, the following examples illustrate the differences in
+behaviour between Pyodide and MicroPython:
+
+```html title="A classic Pyodide gotcha VS MicroPython"
+
+
+
+
+
+```
+
+To address the difference in Pyodide's behaviour, we can use the experimental
+flag:
+
+```html title="experimental create_proxy"
+
+ experimental_create_proxy = "auto"
+
+
+
+
+```
+
+Alternatively, `create_proxy` via the `pyscript.ffi` in both interpreters, but
+only in Pyodide can we then destroy such proxy:
+
+```html title="pyscript.ffi.create_proxy"
+
+
+```
diff --git a/docs/user-guide/filesystem.md b/docs/user-guide/filesystem.md
new file mode 100644
index 0000000..b977a3f
--- /dev/null
+++ b/docs/user-guide/filesystem.md
@@ -0,0 +1,176 @@
+# PyScript and Filesystems
+
+As you know, the filesystem is where you store files. For Python to work there
+needs to be a filesystem in which Python packages, modules and data for your
+apps can be found. When you `import` a library, or when you `open` a file, it
+is on the in-browser virtual filesystem that Python looks.
+
+However, things are not as they may seem.
+
+This section clarifies what PyScript means by a filesystem, and the way in
+which PyScript interacts with such a concept.
+
+## Two filesystems
+
+PyScript interacts with two filesystems.
+
+1. The browser, thanks to
+ [Emscripten](https://emscripten.org/docs/api_reference/Filesystem-API.html),
+ provides a virtual in-memory filesystem. **This has nothing to do with your
+ device's local filesystem**, but is contained within the browser based
+ sandbox used by PyScript. The [files](../configuration/#files)
+ configuration API defines what is found on this filesystem.
+2. PyScript provides an easy to use API for accessing your device's local
+ filesystem. It requires permission from the user to mount a folder from the
+ local filesystem onto a directory in the browser's virtual filesystem. Think
+ of it as gate-keeping a bridge to the outside world of the device's local
+ filesystem.
+
+!!! danger
+
+ Access to the device's local filesystem **is only available in Chromium
+ based browsers**. The maximum capacity for files shared in this way is
+ 4GB.
+
+ Firefox and Safari do not support this capability (yet), and so it is not
+ available to PyScript running in these browsers.
+
+## The in-browser filesystem
+
+The filesystem that both Pyodide and MicroPython use by default is the
+[in-browser virtual filesystem](https://emscripten.org/docs/api_reference/Filesystem-API.html).
+Opening files and importing modules takes place in relation to this sandboxed
+environment, configured via the [files](../configuration/#files) entry in your
+settings.
+
+```toml title="Filesystem configuration via TOML."
+[files]
+"https://example.com/myfile.txt": ""
+```
+
+```python title="Just use the resulting file 'as usual'."
+# Interacting with the virtual filesystem, "as usual".
+with open("myfile.txt", "r") as myfile:
+ print(myfile.read())
+```
+
+Currently, each time you re-load the page, the filesystem is recreated afresh,
+so any data stored by PyScript to this filesystem will be lost.
+
+!!! info
+
+ In the future, we may make it possible to configure the in-browser virtual
+ filesystem as persistent across re-loads.
+
+[This article](https://emscripten.org/docs/porting/files/file_systems_overview.html)
+gives an excellent overview of the browser based virtual filesystem's
+implementation and architecture.
+
+The most important key concepts to remember are:
+
+* The PyScript filesystem is contained *within* the browser's sandbox.
+* Each instance of a Python interpreter used by PyScript runs in a separate
+ sandbox, and so does NOT share virtual filesystems.
+* All Python related filesytem operations work as expected with this
+ filesystem.
+* The virtual filesystem is configured via the
+ [files](../configuration/#files) entry in your settings.
+* The virtual filesystem is (currently) NOT persistent between page re-loads.
+* Currently, the filesystem has a maximum capacity of 4GB of data (something
+ over which we have no control).
+
+## The device's local filesystem
+
+**Access to the device's local filesystem currently only works on Chromium
+based browsers**.
+
+Your device (the laptop, mobile or tablet) that runs your browser has a
+filesystem provided by a hard drive. Thanks to the
+[`pyscript.fs` namespace in our API](../../api/#pyscriptfs), both MicroPython
+and Pyodide (CPython) gain access to this filesystem should the user of
+your code allow this to happen.
+
+This is a [transient activation](https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation)
+for the purposes of
+[user activation of gated features](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).
+Put simply, before your code gains access to their local filesystem, an
+explicit agreement needs to be gathered from the user. Part of this process
+involves asking the user to select a target directory on their local
+filesystem, to which PyScript will be given access.
+
+The directory on their local filesystem, selected by the user, is then mounted
+to a given directory inside the browser's virtual filesystem. In this way a
+mapping is made between the sandboxed world of the browser, and the outside
+world of the user's filesystem.
+
+Your code will then be able to perform all the usual filesystem related
+operations provided by Python, within the mounted directory. However, **such
+changes will NOT take effect on the local filesystem UNTIL your code
+explicitly calls the `sync` function**. At this point, the state of the
+in-browser virtual filesystem and the user's local filesystem are synchronised.
+
+The following code demonstrates the simplest use case:
+
+```python title="The core operations of the pyscript.fs API"
+from pyscript import fs
+
+# Ask once for permission to mount any local folder
+# into the virtual filesystem handled by Pyodide/MicroPython.
+# The folder "/local" refers to the directory on the virtual
+# filesystem to which the user-selected directory will be
+# mounted.
+await fs.mount("/local")
+
+# ... DO FILE RELATED OPERATIONS HERE ...
+
+# If changes were made, ensure these are persisted to the local filesystem's
+# folder.
+await fs.sync("/local")
+
+# If needed to free RAM or that specific path, sync and unmount
+await fs.unmount("/local")
+```
+
+It is possible to use multiple different local directories with the same mount
+point. This is important if your application provides some generic
+functionality on data that might be in different local directories because
+while the nature of the data might be similar, the subject is not. For
+instance, you may have different models for a PyScript based LLM in different
+directories, and may wish to switch between them at runtime using different
+handlers (requiring their own transient action). In which case use
+the following technique:
+
+```python title="Multiple local directories on the same mount point"
+# Mount a local folder specifying a different handler.
+# This requires a user explicit transient action (once).
+await fs.mount("/local", id="v1")
+# ... operate on that folder ...
+await fs.unmount("/local")
+
+# Mount a local folder specifying a different handler.
+# This also requires a user explicit transient action (once).
+await fs.mount("/local", id="v2")
+# ... operate on that folder ...
+await fs.unmount("/local")
+
+# Go back to the original handler or a previous one.
+# No transient action required now.
+await fs.mount("/local", id="v1")
+# ... operate again on that folder ...
+```
+
+In addition to the mount `path` and handler `id`, the `fs.mount` function can
+take two further arguments:
+
+* `mode` (by default `"readwrite"`) indicates the sort of activity available to
+ the user. It can also be set to `read` for read-only access to the local
+ filesystem. This is a part of the
+ [web-standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#mode)
+ for directory selection.
+* `root` - (by default, `""`) is a hint to the browser for where to start
+ picking the path that should be mounted in Python. Valid values are:
+ `desktop`, `documents`, `downloads`, `music`, `pictures` or `videos`
+ [as per web standards](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#startin).
+
+The `sync` and `unmount` functions only accept the mount `path` used in the
+browser's local filesystem.
diff --git a/docs/user-guide/first-steps.md b/docs/user-guide/first-steps.md
new file mode 100644
index 0000000..6a01580
--- /dev/null
+++ b/docs/user-guide/first-steps.md
@@ -0,0 +1,165 @@
+# First steps
+
+It's simple:
+
+* tell your browser to use PyScript, then,
+* tell PyScript how to run your Python code.
+
+That's it!
+
+For the browser to use PyScript, simply add a `
+
+
+
+
+
+```
+
+There are two ways to tell PyScript how to find your code.
+
+* With a standard HTML `
+```
+
+...and here's a `` tag with inline Python code.
+
+```html title="A <py-script> tag with inline code"
+
+import sys
+from pyscript import display
+
+
+display(sys.version)
+
+```
+
+The `
+
+ ```
+
+ Notice how different interpreters can be used with different
+ configurations.
+
+
diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md
index b35b5a0..0351907 100644
--- a/docs/user-guide/index.md
+++ b/docs/user-guide/index.md
@@ -1,485 +1,41 @@
-# Introduction (start here)
+# The PyScript User Guide
-!!! note
-
- This guide provides detailed technical guidance for developers who want
- an in depth explanation of the PyScript platform.
-
- As a result, while we endeavour to write clearly, some of the content in
- this user guide will not be suitable for beginners. We assume the folks who
- will get most from this guide will already have some Python or web
- development experience.
-
- We welcome feedback to help us improve.
-
-This guide is in three parts:
-
-1. The brief overview, context setting and sign-posting contained within this
- page.
-2. The other pages of the guide, referenced from this page, that explore the
- different aspects of PyScript in substantial technical detail.
-3. The example projects, listed at the end of this page, that demonstrate the
- various features of PyScript working together in real-world applications.
-
-We suggest you _read this page in full_: it will ensure you have a
-comprehensive overview of the PyScript platform along with suggestions for
-where next to explore.
-
-When you require depth and technical detail you should consult the other pages
-of this guide that are referenced from this page. They provide clear and
-precise details along with example code fragments and descriptions of the APIs
-available via PyScript.
-
-Finally, the examples listed at the end of this page are all freely available
-and copiously commented on [pyscript.com](pyscript.com). You should consult
-these for practical "real world" use of the various features of the PyScript
-platform. Many of these examples come from contributors to our wonderful
-community. If you believe you have a project that would make a good example,
-please don't hesitate to get in touch.
-
-## What is PyScript?
-
-PyScript is a platform for Python in the browser.
-
-PyScript's aim is to bring together two of the most vibrant technical
-ecosystems on the planet. If the web and Python had a baby, you'd get PyScript.
-
-PyScript works because modern browsers support
-[web assembly](https://webassembly.org/) (abbreviated to WASM) - a virtual
-machine with an open specification and near native performance. PyScript takes
-versions of the Python interpreter compiled to WASM, and makes them easy to use
-from within your browser.
-
-At the core of PyScript is a philosophy of digital empowerment. The web is the
-world's most ubiquitous computing platform, mature and familiar to billions of
-people. Python is one of the world's most popular programming languages:
-easy to teach and learn, used in a plethora of existing domains
-such as data science, games, embedded systems, artificial intelligence and
-film making (to name but a few), and the Python ecosystem contains a huge
-number of libraries to address its many uses.
-
-PyScript brings together the ubiquity and accessibility of the web with the
-power, depth and expressiveness of Python. It means PyScript isn't just for
-programming experts but, as we like to say, for the 99% of the rest of the
-planet who use computers.
-
-## Features
-
-
- - All the web
- -
-
Thanks to the foreign function interface (FFI), PyScript gives you
- access to all the
- web
- APIs implemented by your browser.
-
- The FFI makes it easy for Python to work within your browser, including
- with third party JavaScript libraries that may be included via the
- script
tag.
-
- The FFI is bi-directional ~ it also enables JavaScript to access the
- power of PyScript.
- - All of Python
- -
-
PyScript brings you two Python interpreters:
-
- - Pyodide - the original standard
- CPython interpreter you know and love, but compiled to web
- assembly.
- - MicroPython - a lean and
- efficient reimplementation of Python3 that includes a comprehensive
- subset of the standard library, compiled to web assembly.
-
- Because it is just regular CPython, Pyodide puts Python's deep and
- diverse ecosystem of libraries, frameworks and modules at your disposal. If
- you find yourself encountering some sort of computing problem, there's
- probably a Python library to help you with it. If you're used to using a
- favourite library in Python, now you can use it in your browser and share
- it with the ease of a URL.
- MicroPython, because of its small size (170k) and speed, is especially
- suited to running on more constrained browsers, such as those on mobile
- or tablet devices. It includes a powerful sub-set of the Python standard
- library and efficiently exposes the expressiveness of Python to the
- browser.
- Both Python interpreters supported by PyScript implement the same
- API to bridge the gap between the worlds of Python and the browser.
-
- - Data science built in
- - Python is famous for its extraordinary usefulness in data science
- and artificial intelligence. The Pyodide interpreter comes with many of the
- libraries you need for this sort of work already baked in.
- - Mobile friendly MicroPython
- -
-
Thanks to MicroPython in PyScript, there is a compelling story for
- Python on mobile.
-
- MicroPython is small and fast enough that your app will start quickly
- on first load, and almost instantly (due to the cache) on subsequent
- runs.
- - Parallel execution
- - Thanks to a browser technology called
- web workers
- expensive and blocking computation can run somewhere other than the
- main application thread that controls the user interface. When such work is
- done on the main thread, the browser appears frozen. Web
- workers ensure expensive blocking computation happens elsewhere. Think
- of workers as independent subprocesses in your web page.
- - Rich and powerful plugins
- -
-
As you'll see, PyScript has a small, efficient yet powerful core called
- PolyScript.
- Most of the functionality of PyScript is actually implemented through
- PolyScript's plugin system.
-
- This approach means we get a clear separation of concerns: PolyScript
- can focus on being small, efficient and powerful, whereas the PyScript
- related plugins allow us to build upon the generic features provided by
- PolyScript. More importantly, because there is a plugin system, folks
- _independent of the PyScript core team_ have a way to create their own
- plugins so we get a rich ecosystem of functionality that reflects the
- unique and many needs of PyScript's users.
-
-
-
-## Architecture
-
-There are two important pieces of information you should know about the
-architecture of PyScript:
-
-1. A small, efficient and powerful kernel called
- [PolyScript](https://github.com/pyscript/polyscript) is the foundation
- upon which PyScript and plugins are built.
-2. The stack inside the browser is relatively simple and easy to understand.
-
-Let's dive into each in turn.
-
-### PolyScript
-
-[PolyScript](https://github.com/pyscript/polyscript) is the core of PyScript.
-
-Its purpose is to bootstrap the platform and provide all the necessary core
-capabilities. It is a small, efficient and powerful kernel. Setting aside
-PyScript for a moment, to use *just PolyScript* requires a `
-
-
-
-
-
-