Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Examples/ Example: a dockable panel
Examples

Example: a dockable panel

A complete @tool.view that returns a View — a param() field plus an @action button that calls self.ctx.ui.notify. Runs headless too.

TL;DRA View with no kind is param() fields plus @action buttons (informally a panel when docked in the sidebar). This tool.py declares a GreetPanel with one text field and a Greet action, and @tool.view docks it in the sidebar. self.ctx is injected on mount, so the action calls self.ctx.ui.notify(...). No GTK, so it imports and tests headless.

A View with no kind is param() fields plus @action buttons (informally a panel when docked in the sidebar) — no GTK, so it imports and tests headless. Save this as tool.py, point LSD at it ("Add local package…"), and a Greeter panel docks in the sidebar.

The code#

Python
# tool.py
from lsdtools import Tool, Context, View, action, param

tool = Tool("greeter", label="Greeter")


class GreetPanel(View):
    """A View with no kind: a param() field plus an @action button."""

    name = param(str, default="world", label="Name")
    times = param(int, default=1, min=1, max=10, label="Repeat")

    @action(label="Greet", style="suggested")
    def greet(self):
        # self.ctx is injected when the panel mounts, so the host is one hop away.
        for _ in range(self.times):
            self.ctx.ui.notify(f"Hello, {self.name}!")


@tool.view("greeter.hello", title="Greeter", location="sidebar")
def panel(ctx: Context):
    return GreetPanel()

What each line does#

  • class GreetPanel(View) — a panel is a View. Its fields are param() descriptors, so they get typing, coercion, and the min/max validation (times is clamped to 1–10) for free.
  • @action(label="Greet", style="suggested") — a button. The handler reads the fields as typed attributes (self.name, self.times) — there is no values dict. style is None, "suggested", or "destructive".
  • self.ctx.ui.notify(...)self.ctx is the host Context, injected automatically when the panel mounts, so an action can reach the host UI with no wiring.
  • @tool.view("greeter.hello", title=…, location="sidebar") — docks the View in the sidebar. The decorated function is (ctx) -> View; return a View instance (or its class).

Run it#

You don't need the desktop to exercise the logic. invoke is the one dispatch entry for every front-end, so a test drives the action directly — stub ctx.ui to capture the notifications:

Python
from lsdtools import Context

class FakeUI:
    def notify(self, message, level="info"):
        print(f"[{level}] {message}")

p = GreetPanel(name="Ada", times=2)
p.ctx = Context.testing(ui=FakeUI())
p.invoke("greet")
# → [info] Hello, Ada!
#   [info] Hello, Ada!

Or present the whole panel as text without a display — show("text") renders the fields and actions:

Python
GreetPanel(name="Ada").show("text")
# ── GreetPanel ──
#   Name: 'Ada'
#   Repeat: 1
#   actions: Greet

In the desktop, loading the package puts a Greeter panel in the sidebar: type a name, set the repeat count, and click Greet to fire the notifications.

Try changing it#

  • Add a param(bool, default=False, label="Shout") and upper-case the greeting when it is set.
  • Turn name into a dropdown with param(str, default="world", choices=["world", "team"]).
  • Show a result instead of taking input — build a data view (a View with a kind) and return it from @tool.view.

LearnParams & actions · Everything you show is a View · Commands & actions

StartHow an LSD package fits together · Naming: which is which

ExamplesYour first tool · A complete package

By LSD Team · Last updated Jul 18, 2026 Ask on Discord View as Markdown