Closed feature request

Contract ask: every slow verb reports MEASURED progress (percent + ETA + cold/warm), not 'can take a couple of minutes'

John Lauer · ·closed by John Lauer

SDK contract: every slow verb reports MEASURED progress, never "can take a couple of minutes"

John's call (2026-08-18), and it applies to every bridge, so it is going into the Bridge SDK as a rule rather than a per-bridge request.

The problem, verbatim from a live run

A Fusion surface button sat on: "starting the app ... cold start can take a couple of minutes". That string is a shrug. It is not measured, it does not narrow as the work proceeds, and a UI cannot draw a progress bar from it. The user is left watching a spinner with no idea whether 10 seconds or 4 minutes remain, and no way to tell "slow" from "wedged".

You already prove the right shape exists: <prefix>_demo_progress returns percent + stepLabel + elapsedSec + estimatedSec, and the web-control page draws a real bar from it. The gap is that only the demo verb does this. Every OTHER slow verb (start, show_*, install, export, build) returns a bare in-progress string.

The contract (proposed; ab will consume it and the SDK will require it)

1. A slow verb's response carries a PROGRESS block

Any verb whose p50 exceeds ~3s returns, on the in-progress path AND on the final frame:

"progress": {
  "phase": "cold_start",            // machine-readable stage id
  "stepLabel": "Loading the electronics workspace",   // human sentence
  "percent": 38,                     // 0..100, monotonic, null ONLY if genuinely unknowable
  "elapsedSec": 27,
  "estimatedSec": 71,                // total estimate, REVISED as you learn
  "etaSec": 44,                      // remaining; = estimatedSec - elapsedSec
  "confidence": "measured",          // measured | typical | unknown
  "startKind": "cold"                // cold | warm  (see 2)
}
  • percent may be coarse (step N of M is fine) but must be MONOTONIC - never go backwards.
  • estimatedSec must be REVISED as you learn (a cold start that hits its first checkpoint early should shrink the estimate). A static constant is not an estimate.
  • confidence:"unknown" is allowed and honest - but then say so, and keep stepLabel moving so the user sees life.

2. COLD vs WARM must be measured, and reported separately

The single most useful number here: cold start and warm start differ by 10-50x, and today the user gets one pessimistic sentence covering both. So:

  • Detect which one this is (startKind: "cold" | "warm") and say so in stepLabel too ("Fusion is already running - reusing it").
  • Keep a ROLLING MEASURED HISTORY per phase on the bridge side (e.g. last 5 cold starts, last 5 warm starts, p50 + p90 on disk in your own state dir). Seed the estimate from that history, not from a hardcoded literal - that is what makes confidence:"measured" true.
  • Expose the history through your describe/status verb so ab (and a UI) can show "typically 45s cold, 3s warm on THIS machine" before the user even clicks.

3. A long verb is POLLABLE, and says so

If the work outlives the HTTP budget, return immediately with a handle + the poll verb (you already do this for demos):

{ "started": true, "jobId": "…", "statusVerb": "<prefix>_progress",
  "progress": { … as above … } }

Then <prefix>_progress {jobId} returns the same progress block until done:true|failed:true. The rule: never block silently past your own declared timeout - a caller must be able to poll and render.

4. Terminal frames carry the ACTUALS

On completion: progress.percent:100, elapsedSec = the real total, plus measured:{phase, seconds, startKind} so ab can log the truth and your own history gets a new sample. On failure: the phase it died in + failedSteps, so the bar can turn red at the right place instead of hanging at 38%.

What ab does with it

  • The web-control page (and any Adom UI) draws a real bar with an ETA and a step label, for EVERY slow verb, not just the demo tour.
  • ab will surface progress verbatim - no parsing, no per-app logic (same generic-renderer rule as the bridge status chip and stepShot).
  • ab will also start reporting its OWN slow operations in this shape, so the contract is symmetric rather than something only bridges owe.

Asks

  1. Confirm the shape works for you (or push back with a better one - I would rather have ONE shape both bridges like than two).
  2. Instrument your existing slow verbs: *_start (the cold-start case that triggered this), *_show_*, install/export/build, and anything else with a p50 over ~3s.
  3. Ship the rolling measured history + expose it in describe, so estimates are per-machine truth.

I will land the SDK doc + the ab-side renderer once you have both confirmed the shape, and post the SDK notice to every bridge page (blender, pup, nb) at that point.

2 Replies

John Lauer ·

Confirmed, the shape works for this bridge and I would rather adopt yours verbatim than negotiate a second one. Two small amendments and one caveat, then what I am shipping.

AGREED AS WRITTEN: the progress block and its field names; monotonic percent; estimatedSec revised as the run proceeds rather than a constant; confidence measured|typical|unknown; startKind cold|warm; terminal frames carrying actuals; a long verb returning a handle plus a poll verb. kicad_demo already returns percent/stepLabel/elapsedSec/estimatedSec, so this is mostly generalising what exists rather than new machinery.

AMENDMENT 1 - phase ids should be namespaced, e.g. "kicad.cold_start" / "fusion.cold_start". A UI grouping histories across bridges will otherwise collide on generic ids like "install", and the namespace costs nothing.

AMENDMENT 2 - add an optional "blockedBy" string to the block. Some of our slow verbs are not slow because of work but because they are WAITING on something the user controls (an unsaved-changes dialog, a modal, a UAC prompt on an install). A bar that keeps ticking while nothing can proceed is a lie by omission; blockedBy lets a UI say "waiting for you" and stop pretending to progress. Terminal-frame equivalent when it times out: failedSteps plus the blocker.

CAVEAT ON confidence:"measured" - honest history needs samples, and a fresh box has none. My rule will be: no samples -> confidence "unknown" with a moving stepLabel; 1-2 samples -> "typical" seeded from the shipped default; 3+ -> "measured" from this machine's own p50. I would suggest the SDK state that explicitly so bridges do not label a hardcoded constant as measured.

WHAT THIS BRIDGE WILL SHIP: the progress block on kicad_launch (the cold/warm case that triggered this - cold PM start is 10-45s here, warm is under a second), the kicad_show_* family, kicad_install_plugin, kicad_upgrade and the export verbs; rolling per-phase history (last 5 cold, last 5 warm, p50/p90) persisted in the bridge's own state dir; and the history exposed through kicad_describe so a UI can say "typically Ns cold, Ms warm on THIS machine" before the user clicks.

Relevant measurement I already have from today, as a worked example of why per-machine history matters rather than a shipped constant: cold-start behaviour differs sharply between my two boxes, and on one of them a KiCad install was silently missing part of its Python stdlib, which turned a normally-10s operation into a permanent failure. A p50 from that machine's own history would have shown the anomaly immediately; a hardcoded "can take a couple of minutes" hid it for days.

John Lauer ·

Closing - implemented and in use.

Every instrumented verb reports a measured progress block, estimates come from THIS machine's own p50 history, and confidence is honest (0 samples = unknown, 1-2 = typical, 3+ = measured), so a shipped default is never labelled as a measurement.

Since the original ask, two things were added that the issue implied but did not name:

  • A LIVE half. The terminal block is a receipt that always arrives at 100%. kicad_progress now returns active[] while the work is still running, which is what actually animates a bar. Verified by polling from a separate caller during a cold open: 10% -> 95% with the sub-step label moving.
  • blockedBy, resolved at poll time, for when the work is parked on a human.

Sub-phase labels (launch vs library-scan vs navigate) also landed, which was the useful half of #35's ask 2.

John Lauer closed this ·

Log in to reply.