Developer quickstart¶
This guide builds the smallest useful Groundskeeping application: one page with a section list and a readiness summary. If you are operating an existing application, start with Using a Groundskeeping app instead.
Install¶
Groundskeeping requires Python 3.12 or newer and oa-configurator 1.1 or later within the 1.x series.
uv add groundskeeping
Run the demo first¶
uv run groundskeeping
The demo has Overview, Configuration, Telemetry, and Setup pages and does not depend on Groundworkers or cava-nlp-shard. Its configuration page uses the deterministic fake mutation provider, so no file is written. The Setup page demonstrates an ordinary results table whose highlighted row fills the lower detail pane; the source in src/groundskeeping/demo.py is also a useful composition reference.
Build one page¶
A page is an ordinary Textual widget that satisfies the OperatorPage protocol. Keep application services on the page; the shell passes a narrow PageContext for navigation, rendering, notifications, and wizards.
from textual.widget import Widget
from groundskeeping.app import OperatorApp, OperatorAppSpec
from groundskeeping.contracts import (
NavigationItem,
PageContext,
PageRegistration,
PageRoute,
SectionItem,
SectionNavigation,
SemanticStatus,
SurfaceView,
TreeNode,
TreeView,
)
SETUP_ROUTE = PageRoute(
key="setup",
label="Setup",
purpose="Check whether this environment is ready.",
)
class SetupPage(Widget):
route = SETUP_ROUTE
def activate(self, context: PageContext) -> None:
pass
def deactivate(self, context: PageContext) -> None:
pass
def build_navigation(self, context: PageContext) -> SectionNavigation:
return SectionNavigation(
items=(
SectionItem(
key="database",
label="Database",
status=SemanticStatus.OK,
description="Connection and schema checks",
),
),
title="Setup areas",
)
def landing_view(self, context: PageContext) -> SurfaceView:
return TreeView(
title="Environment readiness",
rows=(
TreeNode(
label="Database",
status=SemanticStatus.OK,
fields={"connection": "available"},
),
),
)
def navigation_selected(self, item: NavigationItem, context: PageContext) -> None:
context.surface.show_view(self.route.key, self.landing_view(context))
def action_selected(self, action_key: str, context: PageContext) -> None:
pass
def row_highlighted(self, row_key: str, context: PageContext) -> None:
pass
def row_selected(self, row_key: str, context: PageContext) -> None:
pass
spec = OperatorAppSpec(
app_id="my-tool",
title="My Tool",
subtitle="environment setup",
pages=(
PageRegistration(
route=SETUP_ROUTE,
factory=lambda context: SetupPage(),
),
),
)
OperatorApp(spec).run()
The shell mounts the page once and calls its lifecycle methods as the operator moves around. On each render it places build_navigation() in the left pane and landing_view() in the result pane.
Add behavior in layers¶
Start with the smallest layer that answers a user need.
| Need | Add | Continue with |
|---|---|---|
| Show current state | A page returning TreeView, TableView, or EmptyView |
Pages and the workbench |
| Run a bounded check | A ViewAction and ActionSpec |
Actions and jobs |
| Collect several related answers | A WizardController |
Setup wizards |
| Inspect an oa-configurator stack | OAConfiguratorAdapter |
Configuration |
| Persist an oa-configurator change | ConfigWorkflowSpec plus ConfigMutationService |
Configuration |
| Show infrastructure measurements | A telemetry source and normalized snapshots | Telemetry |
Start read-only. A page that shows current state and offers Refresh status or Test connection proves routing, service wiring, error presentation, and operator wording before persistence is involved.
Verify the integration¶
uv sync --all-extras --dev
uv run pytest -q
uv run ruff check .
uv run ty check src/
In a consuming application, also test that every page factory can be constructed with its real dependencies and that operation policy rejects unsafe work before a runner or mutation provider performs it.