Skip to content

feat(ux): determinate restore rail, and a shutdown board that is actually visible - #120

Merged
AThraen merged 1 commit into
mainfrom
feat/waiting-states
Sep 6, 2026
Merged

feat(ux): determinate restore rail, and a shutdown board that is actually visible#120
AThraen merged 1 commit into
mainfrom
feat/waiting-states

Conversation

@AThraen

@AThraen AThraen commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Implements the two treatments chosen from the Waiting States options: Quiet Rail for startup, Restore Board for shutdown.

Startup — restore rail

A 25-session restore runs ~131s, with per-session launch cost swinging 12× (0.6s–10.9s, from crash.log). There's no steady rate to extrapolate from, so an indeterminate spinner reads identically at session 2 and session 22.

RestoreRail (2px, under the toolbar) + a 7 / 25 restoring pill give the aggregate; per-session state stays on the placeholder sidebar rows that already exist.

Deliberately non-blocking. Restore doesn't actually block you — placeholder rows let you click into a session the moment it lands — so nothing modal goes in front of it.

The counter advances after the try/catch, not inside it: a session that fails to restore still moves the rail, otherwise one bad session strands it short of full and reads as the exact hang it's meant to rule out.

Shutdown — the overlay was invisible, and that's the real bug

Reported as "more like 1 line - thats hard to see. no spinner or anything."

The markup looked fine — a Grid sibling with Panel.ZIndex="100" and a Storyboard-rotated arc dead centre.

WebView2 is an HwndHost. A native child window is composited by the OS on top of everything WPF renders, and Panel.ZIndex does not enter into it. The overlay was being drawn behind every terminal pane. What reached the user was scrim leaking through the few-pixel gaps between panes — hence a stray line and no spinner, since the spinner sat centred over a pane.

The codebase documents HwndHost for input in three places (#108). Nobody had written down the output half, which is the more expensive one to rediscover because the code looks correct.

Fix: collapse TerminalGrid before showing the overlay. Safe — every pane is about to be disposed, and a frozen terminal is worth nothing mid-shutdown.

Once visible, it earns being more than a spinner:

  • every session named and ticked off — · pending, closing, clean, force-disposed
  • elapsed time per session, an overall k / N bar
  • a budget bar against ClaudeShutdownBudgetMs, green → peach → pink
  • force-disposed sessions are marked, not hidden — the case you most want to see, and it used to happen completely silently
  • ShutdownHint escalates with elapsed time to explain why the wait is long

The hint is explanatory rather than jokey. It has to read well on the four-hundredth shutdown, which is the argument against the "be patient" copy in the original sketch.

Also here

  • Board list scrollbar restyled (scoped to that ScrollViewer). Any setup past ~14 sessions scrolls, so the stock system-coloured bar was on screen for most shutdowns.
  • Board skipped at zero sessions, so --clean runs don't flash a full-window "0 / 0".
  • Corrected a stale comment claiming the adaptive config gate is "still used at SHUTDOWN" — fix(shutdown): flat post-exit pause and a budget sized from measurement #117 removed it from both paths.
  • CLAUDE.md: the airspace rule written down, cross-linked from the existing input-side note.

Verification

Layout rendered offscreen via RenderTargetBitmap against the real markup extracted from MainWindow.xaml (not a copy), at 9 rows and 27 rows, to confirm the card caps at MaxHeight and the list scrolls with header/footer pinned.

304/304 tests pass, 0 warnings.

The airspace fix itself needs a live run — it's about OS compositing and can't be proven headlessly. That's the one thing to eyeball on merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be

…ally visible

Two waiting states, treated differently on purpose.

STARTUP — restore rail
A 25-session restore runs ~131s with per-session launch cost swinging 12x
(0.6s to 10.9s, measured from crash.log). There is no steady rate to
extrapolate from, so an indeterminate spinner reads identically at session
2 and session 22. RestoreRail (2px, under the toolbar) plus a "7 / 25
restoring" pill give the aggregate k-of-N; per-session state stays on the
existing placeholder sidebar rows.

Restore does NOT block the user — placeholder rows mean you can click into
a session the moment it lands — so this stays deliberately out of the way.
Nothing modal.

The counter advances after the try/catch, not inside it, so a session that
fails to restore still moves the rail. Otherwise one bad session strands it
short of full, which reads as exactly the hang it is meant to rule out.

SHUTDOWN — the overlay was invisible, and that is the real bug
Reported as "more like 1 line - thats hard to see. no spinner or anything".
The markup looked correct: a Grid sibling with Panel.ZIndex="100" and a
Storyboard-rotated arc dead centre.

WebView2 is an HwndHost. A native child window is composited by the OS on
top of everything WPF renders, and Panel.ZIndex does not enter into it. The
overlay was being drawn BEHIND every terminal pane. What reached the user
was scrim leaking through the few-pixel gaps between panes — hence a stray
line and no spinner, because the spinner sat centred over a pane.

The codebase already documents HwndHost for input (#108, three separate
call sites). Nobody had written down the output half, which is the more
expensive one to rediscover because the code looks right.

Fix: collapse TerminalGrid before showing the overlay. Safe — every pane is
about to be disposed, and a frozen terminal is worth nothing mid-shutdown.

With it visible, it earns being more than a spinner. The board names every
session and ticks it off (· pending, ◐ closing, ✓ clean, ⨯ force-disposed),
with elapsed time per session, a k/N bar, and a budget bar against
ClaudeShutdownBudgetMs. Force-disposed sessions are marked rather than
hidden: that is the case a user most wants to see and it used to happen
completely silently.

ShutdownHint escalates with elapsed time to explain why the wait is long.
Explanatory, not jokey — it has to read well on the four-hundredth
shutdown, which is the argument against the "be patient" copy I originally
sketched.

Also here:
- Board list scrollbar restyled (scoped to that ScrollViewer). Any setup
  past ~14 sessions scrolls, so the stock system-coloured bar was on screen
  for most shutdowns, not an edge case.
- Board skipped entirely at zero sessions, so --clean runs don't flash a
  full-window "0 / 0".
- Corrected a stale comment claiming the adaptive config gate is "still
  used at SHUTDOWN". #117 removed it from both paths.

Verified: layout rendered offscreen via RenderTargetBitmap against the real
markup extracted from MainWindow.xaml (9 rows and 27 rows, to confirm the
card caps and the list scrolls). The airspace fix itself needs a live run —
it cannot be proven headlessly, since it is about OS compositing.

304/304 tests pass, 0 warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be
@AThraen
AThraen force-pushed the feat/waiting-states branch from d173a0a to 7665ee7 Compare September 6, 2026 17:47
@AThraen
AThraen merged commit a2657b6 into main Sep 6, 2026
1 check passed
@AThraen
AThraen deleted the feat/waiting-states branch September 6, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant