Examples
Full source for every file under the repository examples/ directory.
Run any script with streamlit run path/to/script.py from the repository root (see the root README for the command list).
examples/__init__.py
examples/altair_chart_demo.py
"""Altair ``AltairChart`` demo (requires ``pip install \"streamtree[charts]\"``)."""
from __future__ import annotations
import altair as alt
import pandas as pd
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import AltairChart, Page, VStack
@component
def Demo() -> Element:
df = pd.DataFrame({"x": ["A", "B", "C"], "y": [3, 1, 2]})
chart = (
alt.Chart(df)
.mark_bar()
.encode(x=alt.X("x:N", title=None), y=alt.Y("y:Q", title="Value"))
.properties(height=320, title="Altair via StreamTree")
)
return VStack(AltairChart(chart))
if __name__ == "__main__":
render(Page(Demo()))
examples/app_shell.py
"""Demo: App shell with page config, optional sidebar, and main body."""
from __future__ import annotations
from streamtree import component, render_app
from streamtree.app import App
from streamtree.elements import Button, Page, Text, VStack
from streamtree.state import state
@component
def SidebarNav() -> object:
return VStack(Text("Sidebar"), Text("Use App + render_app for set_page_config."))
@component
def Main() -> object:
n = state(0, key="app_shell_counter")
return Page(
Text("Main column"),
Text(f"Counter: {n()}"),
Button("+1", on_click=lambda: n.increment(1)),
)
if __name__ == "__main__":
render_app(
App(
page_title="App shell demo",
layout="wide",
sidebar=SidebarNav(),
body=Main(),
)
)
examples/async_bg.py
"""Background task demo: ``streamtree.asyncio.submit`` + poll ``status`` / ``result``.
For rerun-polled **progress** from the worker (0.5+), use ``set_task_progress(key=..., value=...)``
with the same ``key`` as ``submit``, and read ``TaskHandle.progress()`` on the main thread.
**0.7+:** use ``submit_many`` for several independent callables, and cooperative cancel
(``TaskHandle.cancel()`` while **running**, then ``is_task_cancel_requested`` /
``complete_cancelled`` inside long workers) — see ``streamtree.asyncio`` module docstring.
"""
from __future__ import annotations
import time
from streamtree import component, render
from streamtree.asyncio import submit, submit_many
from streamtree.elements import Markdown, Page, Text, VStack
@component
def AsyncDemo() -> object:
h = submit(lambda: time.sleep(0.3) or 42, key="demo_slow_job")
status = h.status()
if status == "done":
return VStack(Text(f"Result: {h.result()}"), Markdown("_Task finished._"))
if status == "error":
return Markdown(h.error() or "error")
return VStack(Text(f"Status: {status}"), Markdown("Rerun to poll until **done**."))
@component
def ManySmallJobs() -> object:
a, b = submit_many((("job_a", lambda: 10), ("job_b", lambda: 20)))
if a.status() == "done" and b.status() == "done":
return Text(f"submit_many: {a.result()} + {b.result()} = {a.result()!s} + {b.result()!s}")
return Text(f"Many jobs: {a.status()=!s} {b.status()=!s} (rerun to poll)")
if __name__ == "__main__":
render(Page(VStack(AsyncDemo(), Markdown("---"), ManySmallJobs())))
examples/async_loader_demo.py
"""``match_task`` + :func:`streamtree.asyncio.submit` for loading / ready / error subtrees."""
from __future__ import annotations
import time
from streamtree import asyncio, component, render
from streamtree.core.element import Element
from streamtree.elements import Page, Text, VStack
from streamtree.loading import match_task
@component
def LoaderDemo() -> Element:
def fetch() -> int:
time.sleep(0.15)
return 42
handle = asyncio.submit(fetch, key="loader_demo_value")
return match_task(
handle,
loading=VStack(Text("Loading…")),
ready=lambda n: VStack(Text(f"Ready: result={n!r}")),
error=VStack(Text("Something went wrong.")),
cancelled=VStack(Text("Cancelled.")),
)
if __name__ == "__main__":
render(Page(LoaderDemo()))
examples/async_ordered_loader_demo.py
"""Parallel tasks with stable key order via :func:`submit_many_ordered`.
Run: ``streamlit run examples/async_ordered_loader_demo.py``
``submit_many_ordered`` sorts task keys before calling :func:`streamtree.asyncio.submit_many`,
so :func:`streamtree.loading.match_task_many` ``ready`` tuples align with that order.
"""
from __future__ import annotations
import time
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import Page, Text, VStack
from streamtree.loading import match_task_many, submit_many_ordered
@component
def AsyncOrderedLoaderDemo() -> Element:
def job_a() -> str:
time.sleep(0.05)
return "A"
def job_b() -> str:
time.sleep(0.05)
return "B"
handles = submit_many_ordered({"z": job_b, "m": job_a})
body = match_task_many(
handles,
loading=VStack(Text("Loading m then z (sorted keys)…")),
ready=lambda xs: VStack(Text(f"Results (ordered): {xs}")),
error=VStack(Text("A task failed.")),
)
return Page(
VStack(
Text("## submit_many_ordered + match_task_many"),
body,
Text("Dismiss tasks from your real app before reusing keys; this demo is read-only."),
)
)
if __name__ == "__main__":
render(Page(AsyncOrderedLoaderDemo()))
examples/auth_demo.py
"""Demo: optional login gate (requires ``pip install \"streamtree[auth]\"``).
Replace ``AUTH_CONFIG`` with a real **streamlit-authenticator** YAML-shaped dict
(credentials + cookie). Treat it as **trusted secrets** (same posture as API keys).
See https://github.com/mkhorasani/Streamlit-Authenticator for hashing passwords and
full config layout. This file is a structural example only.
"""
from __future__ import annotations
from streamtree import component, render_app
from streamtree.app import App
from streamtree.auth import AuthGate
from streamtree.elements import Page, Text, VStack
# Minimal shape only — replace before use in production.
AUTH_CONFIG: dict = {
"credentials": {"usernames": {}},
"cookie": {"name": "streamtree_auth", "key": "change_this_random_secret", "expiry_days": 30},
}
@component
def Protected() -> object:
return Page(VStack(Text("You are signed in (example).")))
if __name__ == "__main__":
render_app(App(page_title="Auth demo", body=AuthGate(config=AUTH_CONFIG, child=Protected())))
examples/chart_demo.py
"""Plotly ``Chart`` demo (requires ``pip install \"streamtree[charts]\"``)."""
from __future__ import annotations
import plotly.graph_objects as go
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import Chart, Page, VStack
@component
def Demo() -> Element:
fig = go.Figure(data=go.Bar(x=["A", "B", "C"], y=[3, 1, 2]))
fig.update_layout(margin=dict(l=8, r=8, t=32, b=8), height=320)
return VStack(Chart(fig))
if __name__ == "__main__":
render(Page(Demo()))
examples/counter.py
"""Run with: ``streamlit run examples/counter.py`` from the repo root."""
from __future__ import annotations
from streamtree import component, render
from streamtree.elements import Button, Card, Page, Text
from streamtree.state import state
@component
def Counter():
count = state(0, key="demo_counter")
return Card(
Text(f"Count: {count()}"),
Button("Increment", on_click=lambda: count.increment(1)),
Button("Reset", on_click=lambda: count.set(0)),
)
def streamtree_tree_root():
"""Stable entry for ``streamtree tree examples.counter:streamtree_tree_root``."""
return Page(Counter(), key="page")
if __name__ == "__main__":
render(Page(Counter(), key="page"))
examples/crud_automation_demo.py
"""CRUD helpers: URL id + save-intent counter (Phase 3).
Run: ``streamlit run examples/crud_automation_demo.py``
Uses :mod:`streamtree.crud` alongside normal ``state`` for row data. See ``docs/PHASE3_CRUD.md``.
"""
from __future__ import annotations
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.crud import save_intent_counter, selected_id_from_query
from streamtree.elements import Button, Page, Text, TextInput, VStack
from streamtree.routing import set_query_value
from streamtree.state import state
@component
def CrudAutomationDemo() -> Element:
qid = selected_id_from_query(param="id", default="1")
name = state("Alpha", key="crud_auto_name")
save_count, bump_save = save_intent_counter(key="crud_auto_save")
def pick(id_str: str, nm: str) -> None:
set_query_value(id_str, param="id")
name.set(nm)
return Page(
VStack(
Text("## CRUD automation helpers"),
Text(f"Query id: {qid!r} — bump save intent: {save_count()}"),
Button("Pick id=1 / Alpha", on_click=lambda: pick("1", "Alpha")),
Button("Pick id=2 / Beta", on_click=lambda: pick("2", "Beta")),
TextInput("Name", value=name),
Button("Simulate save click", on_click=bump_save),
)
)
if __name__ == "__main__":
render(Page(CrudAutomationDemo()))
examples/crud_pattern_demo.py
"""In-memory CRUD + ``match_task_many`` / ``submit`` pattern (Phase 3 reference).
Run: ``streamlit run examples/crud_pattern_demo.py``
See ``docs/PHASE3_CRUD.md`` for how this maps to ``DataGrid``, URL filters, and optional extras.
"""
from __future__ import annotations
import time
from typing import Any
from streamtree import asyncio, component, render
from streamtree.core.element import Element
from streamtree.elements import Button, Form, Page, Text, TextInput, VStack
from streamtree.loading import match_task_many
from streamtree.state import state
def _rows() -> list[dict[str, Any]]:
return [{"id": 1, "name": "Alpha"}, {"id": 2, "name": "Beta"}]
@component
def CrudPatternDemo() -> Element:
rows = state(list(_rows()), key="crud_rows")
selected_id = state(1, key="crud_sel")
name_edit = state("Alpha", key="crud_name")
def sync_refs() -> tuple[int, int]:
time.sleep(0.08)
return (1, 2)
h1 = asyncio.submit(lambda: sync_refs()[0], key="crud_prefetch_a")
h2 = asyncio.submit(lambda: sync_refs()[1], key="crud_prefetch_b")
sync_banner = match_task_many(
(h1, h2),
loading=VStack(Text("Loading reference data…")),
ready=lambda _: VStack(Text("Reference checks complete.")),
error=VStack(Text("Reference load failed.")),
)
def select_row(rid: int, current_name: str) -> None:
selected_id.set(rid)
name_edit.set(current_name)
def apply_edit() -> None:
rid = int(selected_id())
name = str(name_edit()).strip()
if not name:
return
updated = [{**r, "name": name} if r["id"] == rid else r for r in rows()]
rows.set(updated)
def add_row() -> None:
name = str(name_edit()).strip() or "New"
nxt = max((r["id"] for r in rows()), default=0) + 1
rows.set([*rows(), {"id": nxt, "name": name}])
selected_id.set(nxt)
row_buttons: list[Element] = []
for r in rows():
rid, nm = r["id"], r["name"]
row_buttons.append(
Button(
f"{rid}: {nm}",
on_click=lambda rid=rid, nm=nm: select_row(rid, nm),
)
)
return VStack(
Text("## CRUD pattern (in-memory)"),
sync_banner,
Text("Select a row, edit the name, then **Apply**."),
*row_buttons,
Form(
TextInput("Name", value=name_edit),
Button("Apply edit", on_click=apply_edit, submit=True),
Button("Add row (uses name field)", on_click=add_row, submit=True),
form_key="crud_form",
),
)
if __name__ == "__main__":
render(Page(CrudPatternDemo()))
examples/datagrid_demo.py
"""``DataGrid`` demo (requires ``pip install \"streamtree[tables]\"``)."""
from __future__ import annotations
import pandas as pd
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import DataGrid, Page, Text, VStack
@component
def Demo() -> Element:
df = pd.DataFrame({"name": ["Ada", "Grace"], "score": [99, 100]})
return VStack(
Text("Editable multi-select grid:"), DataGrid(df, editable=True, selection_mode="multiple")
)
if __name__ == "__main__":
render(Page(Demo()))
examples/datagrid_selection_demo.py
"""``DataGrid`` with ``on_result`` — react to the AgGrid return value each rerun."""
from __future__ import annotations
from typing import Any
import pandas as pd
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import DataGrid, Page, Text, VStack
from streamtree.state import state
@component
def Demo() -> Element:
df = pd.DataFrame({"name": ["Ada", "Grace"], "score": [99, 100]})
summary = state("No AgGrid result yet.", key="ag_summary")
def on_result(result: Any) -> None:
# Runs in the same script run as ``AgGrid``; keep this light.
name = type(result).__name__
summary.set(f"Last AgGrid return: {name}")
return VStack(
Text(summary()),
DataGrid(df, selection_mode="single", on_result=on_result, key="grid_sel"),
)
if __name__ == "__main__":
render(Page(Demo()))
examples/deferred_region_demo.py
"""Deferred region via :class:`DeferredFragment` (``st.fragment`` when available).
Run: ``streamlit run examples/deferred_region_demo.py``
On Streamlit builds with ``st.fragment``, children render inside a fragment for
lower scheduling priority; otherwise they render inline like a normal subtree.
"""
from __future__ import annotations
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import DeferredFragment, Page, Text, VStack
@component
def DeferredRegionDemo() -> Element:
heavy = tuple(Text(f"Row {i}") for i in range(12))
return Page(
VStack(
Text("## DeferredFragment demo"),
Text("Heavy block below is wrapped for fragment scheduling when supported."),
DeferredFragment(*heavy),
)
)
if __name__ == "__main__":
render(Page(DeferredRegionDemo()))
examples/echarts_demo.py
"""Apache ECharts via ``EChartsChart`` (requires ``pip install \"streamtree[charts]\"``)."""
from __future__ import annotations
from streamtree import component, render
from streamtree.core.element import Element
from streamtree.elements import EChartsChart, Page, VStack
@component
def Demo() -> Element:
options = {
"xAxis": {"type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri"]},
"yAxis": {"type": "value"},
"series": [{"data": [120, 200, 150, 80, 70], "type": "bar"}],
"title": {"text": "ECharts via StreamTree"},
}
return VStack(EChartsChart(options, height=360, key="ech1"))
if __name__ == "__main__":
render(Page(Demo()))
examples/model_form.py
"""Pydantic model bound to ``TextInput`` widgets via ``str_text_inputs``."""
from __future__ import annotations
import json
from pydantic import BaseModel, ValidationError
from streamtree import component
from streamtree.app import App
from streamtree.app_context import provider
from streamtree.core.component import render_app
from streamtree.elements import Button, Markdown, Text, ThemeRoot, VStack
from streamtree.forms import (
bind_str_fields,
format_validation_errors,
model_validate_json,
str_text_inputs,
)
from streamtree.state import state
from streamtree.theme import Theme
class Contact(BaseModel):
name: str
email: str
@component
def ContactForm() -> object:
fields = bind_str_fields(Contact, key_prefix="contact_form")
err = state("", key="contact_form_err")
def validate_json() -> None:
try:
payload = {k: fields[k]() for k in fields}
model_validate_json(Contact, json.dumps(payload))
err.set("")
except ValidationError as e:
err.set(format_validation_errors(e))
except (TypeError, ValueError) as e:
err.set(str(e))
return VStack(
ThemeRoot(),
*str_text_inputs(
Contact, bindings=fields, field_labels={"name": "Full name", "email": "Email"}
),
Button("Validate JSON shape", on_click=validate_json),
Markdown(err() or " "),
)
if __name__ == "__main__":
t = Theme(primary_color="#0068c9", custom_css="a { color: var(--st-theme-primary); }\n")
with provider(theme=t):
render_app(
App(
page_title="Model form",
body=VStack(Text("Declarative str fields + Pydantic"), ContactForm()),
)
)
examples/numeric_nav_demo.py
"""Numeric ``NumberInput`` grid from Pydantic fields plus a ``PageLink`` example."""
from __future__ import annotations
from pydantic import BaseModel
from streamtree import component
from streamtree.app import App
from streamtree.core.component import render_app
from streamtree.elements import Page, PageLink, Text, VStack
from streamtree.forms import bind_numeric_fields, number_inputs
class LineItem(BaseModel):
quantity: int = 1
unit_price: float = 0.0
@component
def MiniForm() -> object:
bindings = bind_numeric_fields(LineItem, key_prefix="line_item")
return VStack(
Text("Adjust values (bound to session state via ``bind_numeric_fields``)."),
*number_inputs(
LineItem,
bindings=bindings,
field_labels={"quantity": "Quantity", "unit_price": "Unit price"},
),
PageLink(
"Open About demo page",
page="pages/1_About_demo.py",
),
)
if __name__ == "__main__":
render_app(
App(
page_title="Numeric + navigation",
initial_sidebar_state="expanded",
body=Page(VStack(Text("StreamTree 0.4 forms + PageLink"), MiniForm())),
)
)
examples/overlay_demo.py
"""Demo: Dialog and Popover overlays (Streamlit 1.33+)."""
from __future__ import annotations
from streamtree import component, render_app
from streamtree.app import App
from streamtree.elements import Button, Dialog, Page, Popover, Text, VStack
from streamtree.state import toggle_state
@component
def Main() -> object:
show = toggle_state(key="dlg_open", initial=False)
return Page(
VStack(
Text("Open the dialog or popover below."),
Button("Open dialog", on_click=lambda: show.set(True)),
Dialog("Example", Text("Dialog body"), open=show),
Popover("Details", Text("Popover body")),
)
)
if __name__ == "__main__":
render_app(App(page_title="Overlay demo", body=Main()))
examples/pages/1_About_demo.py
"""Stub multipage file for ``pages_helpers_demo.py`` (Streamlit ``pages/`` layout)."""
import streamlit as st
st.title("About (demo page)")
examples/pages_helpers_demo.py
"""List Streamlit ``pages/*.py`` entries via ``streamtree.helpers.pages`` (0.5.0+).
Use :func:`streamtree.helpers.page_links` (0.8.0+) to turn discovery rows into :class:`PageLink`
widgets without a generator expression.
Run from the repository root (so ``examples/pages/`` sits next to this file):
streamlit run examples/pages_helpers_demo.py
"""
from __future__ import annotations
from streamtree import component, render
from streamtree.elements import Markdown, Page, VStack
from streamtree.helpers import discover_pages, page_links
@component
def Body():
entries = discover_pages(__file__)
if not entries:
return VStack(
Markdown("Add scripts under **`examples/pages/`** to see `PageLink` targets here."),
)
return VStack(
Markdown("**Pages** discovered next to this script:"),
*page_links(entries),
)
if __name__ == "__main__":
render(Page(Body()))
examples/phase2_composite_demo.py
"""Phase 2 composite: ``ErrorBoundary``, ``app_context``, ``Routes``, ``submit_many``.
Run: ``streamlit run examples/phase2_composite_demo.py``
Wraps the tree in :func:`streamtree.app_context.provider` so ``lookup`` works inside
components. ``submit_many`` jobs use stable keys; rerun to poll until both finish.
"""
from __future__ import annotations
from streamtree import component, render
from streamtree.app_context import lookup, provider
from streamtree.asyncio import submit_many
from streamtree.elements import (
Button,
ErrorBoundary,
Markdown,
Page,
Routes,
Text,
Title,
VStack,
)
from streamtree.routing import set_route
@component
def Boom() -> object:
"""Raises on purpose so ``ErrorBoundary`` can show its fallback."""
raise RuntimeError("demo failure")
@component
def ContextBadge() -> object:
return Text(f"app_label from context: {lookup('app_label', default='?')!r}")
@component
def ParallelStrip() -> object:
a, b = submit_many((("demo_a", lambda: 1), ("demo_b", lambda: 2)))
if a.status() == "done" and b.status() == "done":
return Text(f"submit_many done: {a.result()} + {b.result()} = 3")
return Text(f"Parallel jobs: {a.status()=!s} {b.status()=!s} (rerun to poll)")
@component
def App() -> object:
return Page(
VStack(
Title("Phase 2 composite"),
ErrorBoundary(
child=Boom(),
fallback=Markdown("**ErrorBoundary** caught a subtree failure."),
),
ContextBadge(),
Markdown("---"),
Button("Home", on_click=lambda: set_route("home")),
Button("Async strip", on_click=lambda: set_route("async")),
Routes(
routes=(
("home", Text("Use buttons above to switch ``route`` query param.")),
("async", ParallelStrip()),
),
default="home",
),
)
)
if __name__ == "__main__":
with provider(app_label="phase2-composite-demo"):
render(App())
examples/phase2_layout_demo.py
"""Phase 2 layout demo: SplitView, portals, and forms_layout (run with ``streamlit run``)."""
from __future__ import annotations
from pydantic import BaseModel, Field
from streamtree import component, render
from streamtree.elements import (
BottomDock,
Button,
FloatingActionButton,
Markdown,
Page,
Portal,
PortalMount,
SplitView,
Text,
VStack,
)
from streamtree.forms import bind_numeric_fields, bind_str_fields
from streamtree.forms_layout import build_model_from_bindings, model_field_grid
class Profile(BaseModel):
handle: str = Field(min_length=1)
score: int
@component
def Demo() -> Page:
sb = bind_str_fields(Profile, key_prefix="p2demo")
nb = bind_numeric_fields(Profile, key_prefix="p2demo")
grid = model_field_grid(Profile, (("handle", "score"),), str_bindings=sb, numeric_bindings=nb)
def save() -> None:
try:
build_model_from_bindings(Profile, str_bindings=sb, numeric_bindings=nb)
except Exception as exc: # pragma: no cover - demo only
sb["handle"].set(f"error: {exc}")
return Page(
VStack(
Markdown("## SplitView + Portal + BottomDock + FAB"),
SplitView(
narrow=VStack(Text("Narrow strip"), PortalMount(slot="aside")),
main=VStack(
Text("Main column"),
grid,
Button("Validate", on_click=save),
Portal(slot="aside", child=Text("Content for narrow slot")),
),
),
PortalMount(slot="footer"),
Portal(slot="footer", child=Markdown("_Footer via portal_")),
BottomDock(Text("Pinned bottom area (needs streamlit-extras + st._bottom)")),
FloatingActionButton("FAB", key="p2fab"),
)
)
if __name__ == "__main__":
render(Demo())
examples/routed_app.py
"""Multi-page demo: query-param routing + optional Pydantic JSON form."""
from __future__ import annotations
import json
from pydantic import BaseModel, ValidationError
from streamtree import component, render
from streamtree.elements import Button, Form, Markdown, Page, Routes, Text, TextInput, Title, VStack
from streamtree.forms import format_validation_errors, model_validate_json
from streamtree.routing import set_route
from streamtree.state import form_state, state
class Profile(BaseModel):
name: str
email: str
@component
def ProfileForm() -> object:
raw = form_state('{"name":"","email":""}', key="profile_json")
err = state("", key="profile_err")
def submit() -> None:
try:
model_validate_json(Profile, raw.edit_value())
err.set("")
raw.commit()
except (ValidationError, json.JSONDecodeError, TypeError) as e:
if isinstance(e, ValidationError):
err.set(format_validation_errors(e))
else:
err.set(str(e))
return Form(
Title("Profile (JSON)"),
TextInput("JSON payload", value=raw),
Markdown(err() or " "),
Button("Validate & commit", submit=True, on_click=submit),
form_key="profile_form",
)
@component
def Nav() -> object:
return VStack(
Text("Pages use query param `route`:"),
Button("Home", on_click=lambda: set_route("home")),
Button("Profile", on_click=lambda: set_route("profile")),
)
@component
def App() -> object:
return Page(
Nav(),
Routes(
routes=(
(
"home",
VStack(Title("Home"), Text("Open Profile to try Pydantic JSON validation.")),
),
("profile", ProfileForm()),
),
default="home",
),
)
if __name__ == "__main__":
render(App())
examples/streamtree_run_demo.md
# Running StreamTree examples with the optional CLI
Install Typer via the **`[cli]`** extra (see `pyproject.toml`):
```bash
pip install "streamtree[cli]"
The streamtree script delegates to Streamlit’s own CLI:
streamtree run examples/counter.py
streamtree run examples/numeric_nav_demo.py -- --server.headless true
streamtree run examples/pages_helpers_demo.py
Arguments after the script path are forwarded unchanged to python -m streamlit run …. The working directory and exit code are those of the child process.
For support or bug reports, run:
This prints streamtree, streamlit, and Python versions, and whether Typer is importable.
Scaffolding a new app (streamtree init)
Generate app.py (and optionally pages/) in the current directory or under a target path:
Or pass a directory explicitly:
Use --force to overwrite existing generated files. --name sets the App.page_title string in the template.
Templates (0.10.0+): --template (short -t) selects the generated app.py body:
| Value | Purpose |
|---|---|
default |
Current multipage-aware shell (same as omitting --template) |
crud |
Routes + streamtree.crud.save_intent_counter starter |
explore |
column_summary on in-memory rows + Markdown preview |
enterprise |
app_context.provider + streamtree.enterprise event sink stub |
Examples:
streamtree init myapp --template crud --name "My CRUD"
streamtree init myapp -t explore
streamtree init myapp --template enterprise --force
The always-supported path remains:
```