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_bytes → Picture(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
PageStack ← AppShell/Tabs layering.
Build order
Timeline indicator (this issue).
Wizard follow-on (separate issue, once the indicator's API is proven).
Summary
A new
bs.Timelinewidget: a passive status-sequence indicator aimed at theengineering / 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+Formcan fake a click-through wizard, but nothingrenders 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
Timeline, notStepper. For this audience "stepper" collides withstepper motor and biases toward navigation. "Timeline" matches the vertical
status-sequence look, and the icon+connector rendering is the canonical timeline look.
(Schedule loop / worker thread / hardware callback / manual clicks) lives outside
the widget — keeps it model-agnostic. No step-callable execution inside.
StepStatusLiteral):pending | active | running | pass | fail | skipped | done.running+ distinctpass/failare the point.Rendering — NO canvas (all icon swaps + composition)
Every concern validated against existing source:
Picture/icon with a liveimageswap.{n}-circle/-filland{n}-square/-fill(verified present 0–9 in
assets/icons/glyphmap.json).shape='circle'|'square'is a free variant.
-fill= upcoming vs active; a completed step showscheck-circle-fill,not its number; fail =
x-circle-fill; skip =dash-circle.>9fallback (plain dot).Pictureanimation, fully in-memory, no baked.gifasset(fixed pixels can't recolor to accent/theme). Path: render
arrow-repeatin thenode's accent color → PIL-rotate to ~8–12 frames → encode to in-memory animated
WebP/GIF (
BytesIO) →Image.from_bytes→Picture(loop=True)auto-plays it.Every node being a
Picturemeans running is the sameimageswap as any state.Divider(already hasthickness/accent/orient/length).Uniform muted line; all state lives in the nodes. No live-setter needed.
Label/Label(textsignal=...)(e.g.
R = 10.02 kΩ ✓). Vertical orientation shines here.Proposed API
add_stepreturns a handle, not a context manager (a step is data, not a contentcontainer).
Prerequisites
Image.from_frames([imgs], duration=)helper to hide theBytesIOencode for the spinner (independently useful for any procedural animation).Out of scope (separate, later)
Wizard— a separate orchestration widget composingTimeline(horizontal) +PageStack+ a Back/Next/Finish bar + linear/validation gating bound toForm.valid.Pure assembly; decide widget-vs-recipe after
Timelineexists. Mirrors thePageStack←AppShell/Tabslayering.Build order
Timelineindicator (this issue).Wizardfollow-on (separate issue, once the indicator's API is proven).