Header/footer progress indicators + spinner tick loop fix #1

Merged
xenarathon merged 7 commits from worktree-header-footer-progress into main 2026-07-31 21:19:28 -04:00
Owner

Summary

  • Fixes a pre-existing production bug: the spinner.TickMsg handler never refreshed the viewport (body appeared frozen) and unconditionally re-armed the deprecated zero-delay spinner.Tick with no phase check (unthrottled busy loop).
  • Adds a 30-cell rule-style section-progress bar to the header, shown in every phase.
  • Switches the spinner style to Points; generating-phase footer now shows the live spinner + elapsed time.
  • Adds a cosmetic pulsing progress bar to the publishing-phase body (1200ms triangle wave) and elapsed time to its footer.

Full design spec and implementation plan are included in this branch under docs/superpowers/.

Test plan

  • go build ./..., go vet ./..., go test ./... all clean (206 tests, up from a 186 baseline)
  • gofmt -l internal/tui/ clean except a pre-existing, unrelated internal/tui/run.go issue
  • Each of the plan's 6 tasks implemented by a fresh subagent and independently reviewed (spec + quality) — all approved, zero Critical/Important findings
  • Final whole-branch review (opus): Ready to merge = Yes, 0 Critical/Important, 3 Minor items logged as follow-ups
  • Manual smoke check of the animation in a real terminal (recommended but not yet done)
## Summary - Fixes a pre-existing production bug: the spinner.TickMsg handler never refreshed the viewport (body appeared frozen) and unconditionally re-armed the deprecated zero-delay spinner.Tick with no phase check (unthrottled busy loop). - Adds a 30-cell rule-style section-progress bar to the header, shown in every phase. - Switches the spinner style to Points; generating-phase footer now shows the live spinner + elapsed time. - Adds a cosmetic pulsing progress bar to the publishing-phase body (1200ms triangle wave) and elapsed time to its footer. Full design spec and implementation plan are included in this branch under docs/superpowers/. ## Test plan - [x] go build ./..., go vet ./..., go test ./... all clean (206 tests, up from a 186 baseline) - [x] gofmt -l internal/tui/ clean except a pre-existing, unrelated internal/tui/run.go issue - [x] Each of the plan's 6 tasks implemented by a fresh subagent and independently reviewed (spec + quality) — all approved, zero Critical/Important findings - [x] Final whole-branch review (opus): Ready to merge = Yes, 0 Critical/Important, 3 Minor items logged as follow-ups - [ ] Manual smoke check of the animation in a real terminal (recommended but not yet done)
Plan for the header/footer progress indicators feature, written after
internal panel + salyut review of the design spec.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The spinner.TickMsg case advanced m.spinner's frame and re-batched the
tick but never called m.refreshViewport(), so the body froze on screen
while the frame index and elapsed counter advanced invisibly. It also
batched bubbles' deprecated zero-delay spinner.Tick alongside
m.spinner.Update()'s own FPS-paced cmd, with no phase check — an
unthrottled busy loop that outlived the phase that started it.

Now: refresh the viewport, return only the paced cmd, and return nil
once the phase leaves phaseGenerating/phasePublishing.
Replaces the header's plain 'Section N/M' text with a 30-cell rule-style
bar (━/╺, no percentage number) plus the same count as its label. The bar
is a pure render of (m.section+1)/len(m.post.Sections) via ViewAs(), so
there is no new Model state, Update wiring or Msg case. The 0-section
branch keeps its plain 'No sections' text and remains the
division-by-zero guard.
hintForPhase()'s phaseGenerating case now prepends m.spinner.View() and
appends formatDuration(time.Since(m.genStart)), reusing the state
generateCmd() already maintains — no new Model fields. The spinner style
moves from the unset default (Line) to Points at both construction sites;
purely cosmetic.
publishCmd() returned a single blocking Cmd with no batched tick, so
nothing re-rendered the view while the HTTP call was in flight. It now
stamps m.publishStart (new Model field) and batches one bootstrap
spinner.Tick alongside the publish closure, reusing the same
TickMsg loop generateCmd() already established.
phasePublishing's body was a hardcoded 'Publishing…' literal that sat
static for the whole HTTP round trip. It now renders a bar pulsing
0%->100%->0% on a 1200ms triangle wave derived purely from
time.Since(m.publishStart) — no stored animation state — wired through
refreshViewport() so the spinner tick loop actually re-renders it.

The motion is cosmetic: WriteFreely's Create is one round trip with no
incremental signal to report.
redakt: publishing footer shows elapsed time
Some checks failed
CI / build / test / lint (pull_request) Failing after 5m49s
77a033f971
hintForPhase()'s phasePublishing case returns 'Publishing… (Zs)' instead
of the static 'Publishing…'. No spinner glyph (the body bar already
signals work) and deliberately no Ctrl+C hint: Ctrl+C quits without
cancelling the in-flight request, a pre-existing limitation this change
does not paper over.
xenarathon deleted branch worktree-header-footer-progress 2026-07-31 21:19:28 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
xenarathon/redakt!1
No description provided.