Skip to content

setup_screen

setup_screen

Verdict + decision modal for the first-run / re-run host setup flow.

Renders the current terok_sandbox.SetupVerdict with a contextual blurb and a Run / Skip choice — no subprocess plumbing here. The actual terok setup invocation rides on top of WorkerLogScreen, pushed by the TUI's first-run flow worker after this screen dismisses with SetupOutcome.SHOULD_RUN.

The verdict probe (terok_sandbox.needs_setup) is the same one terok task run enforces in terok.cli.commands.task._setup_verdict_or_exit — so a verdict of OK short-circuits with a banner instead of nudging the user toward a slow re-run, and a STALE_AFTER_DOWNGRADE refuses outright with the same wording the CLI uses.

SetupOutcome

Bases: Enum

User's decision on SetupScreen.

The screen does not run setup itself; it only collects intent. Translating the outcome to host-state changes is the caller's job:

  • SHOULD_RUN — user clicked Run; caller should dispatch terok setup to the console-log registry and push a WorkerLogScreen view over the returned entry.
  • SKIPPED — user dismissed before running; the caller leaves the host alone.
  • REFUSED — verdict is STALE_AFTER_DOWNGRADE; the only available exit, mirroring the CLI exit-4 contract.
  • CANCELLED — Esc on the pre-run screen; treated as a soft skip but distinguishable for telemetry.

SHOULD_RUN = 'should_run' class-attribute instance-attribute

SKIPPED = 'skipped' class-attribute instance-attribute

REFUSED = 'refused' class-attribute instance-attribute

CANCELLED = 'cancelled' class-attribute instance-attribute

SetupScreen(verdict=None)

Bases: ModalScreen[SetupOutcome]

Modal that surfaces the current setup verdict and asks whether to run it.

Two button layouts:

  1. Healthy / non-OK verdict — Skip + Run buttons. Clicking Run dismisses with SetupOutcome.SHOULD_RUN so the parent can dispatch terok setup to the console-log registry and open a WorkerLogScreen view of its streaming output.
  2. Downgrade verdict — Run is hidden; the only exit dismisses with SetupOutcome.REFUSED. The CLI refuses with exit code 4 here; this mirrors that contract by not offering an option that would make the contract meaningless.

Build the screen with an optional pre-fetched verdict.

Verdict probing is normally done on the main thread before the screen is pushed (so the caller can decide whether to show it at all). Falling back to a fresh probe inside the screen keeps direct invocations from the command palette working when no caller pre-fetched it.

Source code in src/terok/tui/setup_screen.py
def __init__(self, verdict: SetupVerdict | None = None) -> None:
    """Build the screen with an optional pre-fetched verdict.

    Verdict probing is normally done on the main thread before the
    screen is pushed (so the caller can decide whether to show it
    at all).  Falling back to a fresh probe inside the screen
    keeps direct invocations from the command palette working
    when no caller pre-fetched it.
    """
    super().__init__()
    self._verdict = verdict if verdict is not None else needs_setup()

BINDINGS = [Binding('escape', 'close', 'Close')] class-attribute instance-attribute

CSS = '\n SetupScreen {\n align: center middle;\n }\n\n #setup-dialog {\n width: 80;\n max-width: 100%;\n height: auto;\n max-height: 80%;\n border: heavy $primary;\n border-title-align: right;\n background: $surface;\n padding: 1 2;\n }\n\n #setup-headline {\n height: auto;\n margin-bottom: 1;\n }\n\n #setup-blurb {\n color: $text-muted;\n height: auto;\n margin-bottom: 1;\n }\n\n #setup-buttons {\n height: auto;\n align-horizontal: right;\n }\n\n #setup-buttons Button {\n margin-left: 1;\n }\n ' class-attribute instance-attribute

compose()

Build the dialog: headline, blurb, action buttons.

Source code in src/terok/tui/setup_screen.py
def compose(self) -> ComposeResult:
    """Build the dialog: headline, blurb, action buttons."""
    dialog = Vertical(id="setup-dialog")
    dialog.border_title = "Terok host setup"
    with dialog:
        yield Static(_VERDICT_HEADLINE[self._verdict], id="setup-headline")
        yield Label(self._blurb_for(self._verdict), id="setup-blurb")
        with Horizontal(id="setup-buttons"):
            yield from self._buttons_for(self._verdict)

action_close()

Esc dismisses with SetupOutcome.CANCELLED (or REFUSED on a downgrade).

Source code in src/terok/tui/setup_screen.py
def action_close(self) -> None:
    """Esc dismisses with [`SetupOutcome.CANCELLED`][terok.tui.setup_screen.SetupOutcome.CANCELLED] (or REFUSED on a downgrade)."""
    self.dismiss(self._cancel_outcome())

on_button_pressed(event)

Route the three button IDs to their dismissal outcomes.

Source code in src/terok/tui/setup_screen.py
def on_button_pressed(self, event: Button.Pressed) -> None:
    """Route the three button IDs to their dismissal outcomes."""
    match event.button.id:
        case "setup-run":
            self.dismiss(SetupOutcome.SHOULD_RUN)
        case "setup-skip":
            self.dismiss(SetupOutcome.SKIPPED)
        case "setup-close":
            self.dismiss(self._cancel_outcome())