Skip to content

feat(streaming): collapse old interim progress notes after 3 visible (#2403)#3574

Closed
rodboev wants to merge 1 commit into
nesquena:masterfrom
rodboev:pr/interim-collapse
Closed

feat(streaming): collapse old interim progress notes after 3 visible (#2403)#3574
rodboev wants to merge 1 commit into
nesquena:masterfrom
rodboev:pr/interim-collapse

Conversation

@rodboev

@rodboev rodboev commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Thinking Path

  • PR Preserve live agent timeline across session switches #2347 (v0.51.76) reworked interim-assistant accumulation to use \n\n separators for readability, but this increased vertical consumption on long tool-heavy turns.
  • The issue proposed four options; option 3 (cap visible count with an "N earlier" summary) balances readability with vertical space.
  • The collapse mirrors the existing thinking-card collapse pattern, keeping the UX consistent.
  • The state is per-turn/per-stream, so it resets naturally when the stream completes.

What Changed

  • static/messages.js: after the 4th interim_assistant event in a single turn, hide all but the last 3 rendered interim blocks and prepend a "Show N earlier updates" toggle. Clicking the toggle reveals all hidden blocks and switches to a "Collapse" toggle. Uses data-interim="1" attribute marking and a named INTERIM_COLLAPSE_THRESHOLD constant.
  • static/style.css: ~11 lines for .interim-collapse-toggle and .interim-collapsed classes.
  • tests/test_issue2403_interim_collapse.py: 14 static-analysis tests across 2 classes (handler structure invariants and CSS rule assertions).

Why It Matters

Long tool-heavy turns with 6+ interim progress notes scroll the final answer off-screen on 14" laptops and smaller. Collapsing old notes to a summary line recovers the vertical space while keeping all notes accessible via expand.

Verification

pytest tests/test_issue2403_interim_collapse.py -v --timeout=60
pytest tests/ -v --timeout=60

Manual: trigger a long tool-heavy turn (e.g., a multi-step agent task). After the 4th interim progress note, verify that earlier notes collapse into an "N earlier updates" link, and clicking the link expands them.

Risks / Follow-ups

  • The collapse threshold (3 visible) is a judgment call. If users find it too aggressive, it can be increased or made configurable.
  • Only collapses within a single turn's interim notes. Cross-turn assistant messages are unaffected.
  • Option 2 from the issue (tighten \n\n to \n in Activity groups) is orthogonal and could be done separately.

Model Used

Claude Opus 4.8 via Claude Code CLI

Closes #2403

@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR collapses old interim progress notes after 3 are visible in a single turn, recovering vertical space during long tool-heavy turns without discarding earlier notes. The implementation marks each rendered interim block with data-interim="1", hides all but the latest 3 via a CSS display:none class, and inserts a Show N earlier updates toggle that respects the user's expand state across subsequent events.

  • static/messages.js: The interim_assistant SSE handler gains a INTERIM_COLLAPSE_THRESHOLD=3 guard, DOM marking, and a toggle element with an expanded-state flag (toggle.dataset.expanded) that prevents re-collapsing when the user has explicitly expanded the list.
  • static/style.css: Adds .interim-collapsed and .interim-collapse-toggle rules (~11 lines), self-contained with no conflicts.
  • tests/test_issue2403_interim_collapse.py: 14 static-analysis tests pin structural invariants (attribute ordering, CSS rules, threshold constant) following the existing static-analysis test pattern.

Confidence Score: 5/5

Safe to merge; the change is well-scoped to the interim-note rendering path and does not affect final message delivery or session state.

The collapse logic is isolated to the interim_assistant event handler, the expanded-state guard correctly prevents user actions from being reverted, and the CSS additions are non-conflicting. No data paths, storage, or cross-turn rendering is touched.

No files require special attention; static/messages.js carries the most logic but the change is localized and well-tested by the accompanying static-analysis suite.

Important Files Changed

Filename Overview
static/messages.js Adds interim-note collapse logic inside the interim_assistant SSE handler: marks each rendered block with data-interim="1", hides all but the last 3 via interim-collapsed, and inserts a Show N earlier updates toggle. The expanded-state guard (toggle.dataset.expanded) correctly prevents the user's expand action from being reverted on subsequent events.
static/style.css Adds 11 lines for .interim-collapsed { display:none }, .interim-collapse-toggle (cursor, color, font-size, margin, display), and a :hover underline rule — clean, self-contained, no conflicts.
tests/test_issue2403_interim_collapse.py 14 static-analysis tests across two classes verifying structural invariants (constant presence, attribute marking, collapse class, toggle creation, ordering of attribute-set vs flush vs reset) and CSS rule existence; follows the existing static-analysis pattern in the repo.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[interim_assistant SSE event] --> B{alreadyStreamed?}
    B -- yes --> C[flush + finalize, no collapse]
    B -- no --> D[push to visibleInterimSnippets\nensureAssistantRow\nset data-interim=1]
    D --> E[_flushPendingSegmentRender]
    E --> F{visibleInterimSnippets.length\n> THRESHOLD=3?}
    F -- no --> G[recordActivityBoundary\n_resetAssistantSegment]
    F -- yes --> H[query all data-interim blocks\ncompute toHide = all except last 3]
    H --> I{.interim-collapse-toggle\nalready exists?}
    I -- no --> J[create toggle span\nattach click handler\ninsert before toHide 0]
    I -- yes --> K{toggle.dataset.expanded?}
    J --> K
    K -- expanded=1 --> L[skip re-collapse\nstillHidden=0\nleave toggle as Collapse]
    K -- not expanded --> M[add interim-collapsed to toHide\nupdate toggle text to Show N earlier updates]
    L --> G
    M --> G
Loading

Reviews (6): Last reviewed commit: "feat(streaming): collapse old interim pr..." | Re-trigger Greptile

Comment thread static/messages.js Outdated
Comment thread static/messages.js
Comment thread static/messages.js
Comment thread static/messages.js
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Heads-up: #3401 (live-to-final assistant reply redesign) shipped in v0.51.294, and we're now at v0.51.301. This PR (#2403 (collapse interim progress notes)) is on a stale base and no longer applies cleanly onto current master — the conflict is in static/messages.js, which #3401 rewrote.

#3401 reworked how interim process prose is rendered (folded Worklog model), so the 'collapse after 3 visible' logic needs to target the new structure.

Could you rebase onto current master and re-push? Once rebased it'll gate cleanly and I'll fast-track it. I held off rebasing it myself because it touches the freshly-redesigned surface and you're best placed to re-express the intent against the new architecture (rather than me risk reconstructing it wrong). The underlying fix is wanted — it's purely that the base moved out from under it when #3401 landed. Thanks @rodboev!

@nesquena-hermes nesquena-hermes added hold changes-requested Maintainer left detailed feedback requesting changes; PR is waiting on author to address labels Jun 6, 2026
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Cluster review re-confirm (#3400 live-to-final) — rework (rebase), NOT superseded

Re-checked this against current master in the live-to-final cluster sweep (post #3401 / #3763 / v0.51.309). Confirming the disposition explicitly:

  • Not superseded. Collapsing old interim progress notes after n visible is not implemented anywhere on master today — I grepped the shipped messages.js/ui.js for the concept and found nothing equivalent. This is a genuine, distinct improvement we want.
  • Needs a rework = rebase. Still hold + changes-requested for the same reason as my earlier comment: it's branched pre-Redesign live-to-final assistant replies #3401 and conflicts in the exact regions Redesign live-to-final assistant replies #3401 rewrote, so it's contributor-rebase territory (re-expressing the intent against the new live-turn architecture is your call, not a force-rebase from us).

No action needed beyond a rebase onto current master; ping me when it's rebased and I'll re-gate fresh (full suite + Codex + Opus). The concept stays wanted. Thanks @rodboev.

@rodboev rodboev force-pushed the pr/interim-collapse branch from bb5c5e4 to acc6c48 Compare June 7, 2026 06:38
@rodboev

rodboev commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current master and re-expressed this against the post-#3401 live interim-rendering path. Force-pushed at acc6c486.

  1. Kept the current live-turn / activity-boundary flow intact in static/messages.js and inserted the collapse logic around the current interim_assistant segment lifecycle instead of replaying the pre-Redesign live-to-final assistant replies #3401 hunk.
  2. Preserved the manual-expand guard so new interim events do not re-collapse a block the user explicitly opened.
  3. Kept the scoped CSS + static regression coverage for the threshold / toggle / ordering invariants.

Focused verification I ran for this localized rework:

  • npx eslint --no-config-lookup -c eslint.runtime-guard.config.mjs "static/**/*.js"
  • python "C:\Apps\hermes\run-tests.py" tests\test_issue2403_interim_collapse.py tests\test_bugfix_sweep.py -v --timeout=60

This should be ready for the fresh re-gate you mentioned.

nesquena-hermes added a commit that referenced this pull request Jun 8, 2026
…3574) (#3848)

* feat(streaming): collapse old interim progress notes after 3 visible (#2403)

* fix(streaming): delegated handler for interim-collapse toggle survives live-turn restore

The interim-collapse toggle attached its click listener via per-element
addEventListener at creation time. snapshotLiveTurnHtmlForSession /
restoreLiveTurnHtmlForSession rebuild the live turn via outerHTML/innerHTML
on session switch, which strips JS listeners — so a restored toggle was
visible but inert and the collapsed interim notes became permanently
unreachable for the rest of the turn.

Replace with a stateless document-level delegated click handler
(_interimCollapseDelegatedClick) that resolves the toggle via closest(),
reads state from the DOM (.interim-collapsed) + data-threshold, and works
on both freshly-created and innerHTML-restored toggles. Add 4 regression
tests pinning the delegated-handler contract.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

* Release v0.51.333 — Release KW (collapse old interim progress notes, #3574)

Collapse old interim progress notes after 3 visible during a live turn
(#3574, @rodboev). Maintainer fix during re-gate: replaced the per-element
toggle listener with a stateless document-level delegated handler so the
toggle survives the live-turn DOM restore (Codex caught: innerHTML rebuild
dropped the listener → collapsed notes unreachable). Full suite 8303,
ESLint/scope-undef CLEAN, Opus SHIP-safe, Codex SAFE-TO-SHIP after fix,
collapse + manual-expand-guard + restore-path delegated handler all live-verified.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Hermes Agent <hermes-agent@nesquena-hermes.local>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in v0.51.333 (Release KW) via #3848 — thanks @rodboev! 🎉

I deep-reviewed this (Opus + Codex + live browser drive against master), rebased it onto current master, and shipped it with one fix.

What landed: once more than 3 interim progress notes accumulate during a live turn, the older ones collapse behind a "Show N earlier updates" toggle so the latest note stays in view. Manual expansion is sticky — new interim events won't re-hide what you opened (I verified this live: 5 interims → 2 hidden; expand → 0 hidden; new interim while expanded → stays open).

Fix I added (Codex catch): the toggle attached its click listener via per-element addEventListener. But snapshotLiveTurnHtmlForSession / restoreLiveTurnHtmlForSession rebuild the live turn via innerHTML on session switch, which strips JS listeners — so after switching away and back mid-turn, a restored toggle was visible but inert, leaving the collapsed notes permanently unreachable. I replaced it with a stateless document-level delegated handler (_interimCollapseDelegatedClick) that resolves the toggle via closest(), reads state from the DOM + a data-threshold attribute, and survives the restore. Verified live on the exact restore scenario (markup re-parsed via innerHTML → click still toggles). Added 4 regression tests pinning the delegated-handler contract.

Gate: full suite 8303 passed, ESLint runtime + scope-undef + ruff clean, Opus SHIP-safe, Codex SAFE-TO-SHIP (after the fix), CI 11/11 green. Co-authored to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested Maintainer left detailed feedback requesting changes; PR is waiting on author to address hold

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interim assistant progress notes — consider collapsing or capping on long tool-heavy turns

2 participants