# Serve and embed a spaday app This guide shows you how to deliver a spaday UI to a browser — from "spaday runs the whole app" down to "spaday is one node on a page you already own." Each rung hands more control to the host, and every rung takes the same generation options. (For keeping the UI in sync with a server-side model, see [Sync over transports](transports.md); for a notebook, see [Use in a notebook](notebook.md).) There is **no hand-written HTML**: spaday generates the bootstrap page from your Python description. | Rung | spaday owns | Seam | | ---------------- | ---------------------- | -------------------------------------------------------- | | No HTML | the whole app | `serve(page, …)` | | Some HTML | a sub-path of your app | `mount(app, page, prefix=…)` | | Full custom HTML | one node in your page | `bootstrap(fragment=True, target=…)` + `tree_json(page)` | | Notebook | a cell's widget | `Widget(component)` | `page` is a built component **or a zero-arg callable returning one** — a callable is re-rendered per request, so the tree can reflect current state. ## Serve a whole app `serve` creates a Starlette app and mounts your page on it: it generates the bootstrap HTML, hosts the tree at `/tree.json`, and serves core JS at `/js`. Pull a component library into `
` with `packages=`, add your own routes with `routes=`, and run lifetime coroutines with `background=`: ```python import uvicorn from spaday.backends.starlette import serve from spaday_webawesome import WaButton app = serve( lambda: WaButton(variant="brand").text("Hi"), packages=["webawesome"], # pull WebAwesome's styles + catalog into head="", title="my app", ) if __name__ == "__main__": uvicorn.run(app, host="127.0.0.1", port=8000) ``` Component integrations are peer packages: `spaday-webawesome`, `spaday-lightweight-charts`, `spaday-regular-layout`, `spaday-regular-table`, and `spaday-perspective` all use `packages=` (see below). `serve` is the one-line happy path — it is `mount` onto a fresh app, so drop to `mount` when the app is yours. In a source checkout it serves built assets from `js/`; from a wheel it automatically serves packaged `spaday/extension` assets. Use `layout="source"` or `layout="installed"` only to override detection, such as when supplying a matching custom `js=` directory. ## Install an external component package External integrations use one `ComponentPackage` descriptor for both `` tags and static routes. An application can pass the descriptor directly or select it by Python path: ```python from spaday_trees import package as trees app = serve(page, packages=[trees]) app = serve(page, packages=["spaday_trees:package"]) ``` An integration package defines that descriptor beside its built assets: ```python from pathlib import Path from spaday import ComponentPackage package = ComponentPackage( name="trees", assets_dir=Path(__file__).parent / "extension", assets=(("css", "trees.css"), ("js", "trees.js")), ) ``` To make the short form `packages=["trees"]` available, publish the same object as a Python packaging entry point: ```toml [project.entry-points."spaday.component_packages"] trees = "spaday_trees:package" ``` Entry-point packages are opt-in: spaday loads only names selected by the application, never every installed integration. All four backends serve each selected descriptor's `assets_dir` at `{prefix}/components/{name}/`; `bootstrap` emits its CSS and module-script URLs from the same `assets` list. This registration is host-side only: component tags and props already cross the generic spaday tree, so an integration needs no Rust plugin. ## Embed in an existing app `mount` adds spaday's routes to an app **you** already have, under a `prefix` — it touches nothing else. The page, tree, `/js`, **and your supplied `routes`** are all prefixed, so a wired panel's generated websocket URL lines up with its endpoint (pass the *unprefixed* path; `mount` adds the prefix). `mount` only adds routes — **the host owns the app's lifespan**, so run any background work in your own lifespan: ```python from starlette.applications import Starlette from starlette.routing import Route from spaday.backends.starlette import mount app = Starlette(routes=[Route("/", my_own_homepage)]) # your app, your routes mount(app, page, prefix="/panel", packages=["webawesome"]) # spaday lives only under /panel ``` Backends ship for **Starlette/FastAPI**, **aiohttp**, **Flask**, and **Tornado** — import `serve`/`mount` from `spaday.backends.