Skip to content

feat: Timeline — passive status-sequence indicator widget #311

Description

@israel-dryer

Summary

A new bs.Timeline widget: a passive status-sequence indicator aimed at the
engineering / science / hobbyist audience — for procedure / test / calibration /
acquisition sequences (a "run and watch" status display, not a consumer checkout
wizard). Designed in a planning session; this issue captures the locked design.

Why / value

This fills a real gap. Tabs + Form can fake a click-through wizard, but nothing
renders a live pass/fail/running sequence — exactly what instrument/lab/test UIs
want. The value is in the async/status states, not navigation.

Core decisions

  • Name = Timeline, not Stepper. For this audience "stepper" collides with
    stepper motor and biases toward navigation. "Timeline" matches the vertical
    status-sequence look, and the icon+connector rendering is the canonical timeline look.
  • Passive. The widget only renders status you set from code. Automation
    (Schedule loop / worker thread / hardware callback / manual clicks) lives outside
    the widget — keeps it model-agnostic. No step-callable execution inside.
  • State model (a StepStatus Literal): pending | active | running | pass | fail | skipped | done. running + distinct pass/fail are the point.

Rendering — NO canvas (all icon swaps + composition)

Every concern validated against existing source:

  • Nodes = Picture/icon with a live image swap.
    • Numbered nodes use existing glyphs {n}-circle/-fill and {n}-square/-fill
      (verified present 0–9 in assets/icons/glyphmap.json). shape='circle'|'square'
      is a free variant.
    • hollow vs -fill = upcoming vs active; a completed step shows check-circle-fill,
      not its number
      ; fail = x-circle-fill; skip = dash-circle.
    • Numbered only works 0–9 → need a >9 fallback (plain dot).
  • Running spinner = Picture animation, fully in-memory, no baked .gif asset
    (fixed pixels can't recolor to accent/theme). Path: render arrow-repeat in the
    node's accent color → PIL-rotate to ~8–12 frames → encode to in-memory animated
    WebP/GIF (BytesIO) → Image.from_bytesPicture(loop=True) auto-plays it.
    Every node being a Picture means running is the same image swap as any state.
  • Connectors = static Divider (already has thickness/accent/orient/length).
    Uniform muted line; all state lives in the nodes. No live-setter needed.
  • Labels / per-step result lines = Label / Label(textsignal=...)
    (e.g. R = 10.02 kΩ ✓). Vertical orientation shines here.

Proposed API

tl = bs.Timeline(
    orientation="vertical",   # "vertical" (default) | "horizontal"
    accent="primary",
    numbered=False,           # status-glyph nodes vs numbered badges
    shape="circle",           # "circle" | "square"
    clickable=False,
    density=None,
    steps=None,               # declarative, via the option data bag (normalize_option)
)

step = tl.add_step(
    "Calibrate sensor",
    icon="target",            # optional; else status glyph
    description="Zero the offset",
    status="pending",
    on_select=handler,        # optional click handler
)

# live Step handle (like TreeNode / Image handle)
step.status  = "running"
step.status  = "pass"
step.message = "R = 10.02 kΩ"
step.disabled = True

tl.current = 2                # live prop; repaints + emits
tl.on_step_select(lambda e: ...)   # → StepEvent(index, step)

add_step returns a handle, not a context manager (a step is data, not a content
container).

Prerequisites

  • None hard.
  • Optional convenience: an Image.from_frames([imgs], duration=) helper to hide the
    BytesIO encode for the spinner (independently useful for any procedural animation).

Out of scope (separate, later)

  • Wizard — a separate orchestration widget composing Timeline (horizontal) +
    PageStack + a Back/Next/Finish bar + linear/validation gating bound to Form.valid.
    Pure assembly; decide widget-vs-recipe after Timeline exists. Mirrors the
    PageStackAppShell/Tabs layering.

Build order

  1. Timeline indicator (this issue).
  2. Wizard follow-on (separate issue, once the indicator's API is proven).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions