Skip to content

Fix Transparent Streaming live anchor scene replay - #5257

Merged
3 commits merged into
nesquena:masterfrom
franksong2702:franksong2702/fix-transparent-live-snapshot
Jun 30, 2026
Merged

3 commits merged into
nesquena:masterfrom
franksong2702:franksong2702/fix-transparent-live-snapshot

Conversation

@franksong2702

Copy link
Copy Markdown
Contributor

Thinking Path

  • Live-to-Final rendering treats the anchor activity scene as the durable source of visible live activity.
  • Compact Worklog already consumes runtime_journal_snapshot.anchor_activity_scene during live reattach/reload.
  • Transparent Streaming skipped that live scene because renderLiveAnchorActivityScene() returned before the compact-only gate.
  • That made the same running session show complete process prose in Compact Worklog but miss process prose in Transparent Streaming.
  • This PR keeps Compact Worklog behavior intact and adds the missing Transparent Streaming live anchor-scene render path.

Refs #3400.
Refs #2361.

What Changed

  • Added a Transparent Streaming live anchor-scene renderer that consumes activity_scene_v1 rows before the compact-only gate.
  • Reuses the existing transparent anchor row renderer for prose, tool, and thinking rows.
  • Marks live anchor rows with live/session/stream ownership metadata and clears stale legacy live activity rows before rebuilding.
  • Keeps legacy live assistant segments hidden as metadata anchors while the anchor scene owns visible live activity.
  • Keeps settled Transparent rendering intact by only applying the settled marker for settled rows and adding a behavior regression for that marker.

Why It Matters

Transparent Streaming should not show less visible progress than Compact Worklog for the same live turn. When a runtime journal snapshot already contains ordered process prose and tool activity, both display modes should rebuild from that same source of truth instead of falling back to a partial live tail.

Verification

  • ./scripts/test.sh tests/test_live_to_final_anchor_visible_order.py tests/test_issue3820_chat_activity_display_mode.py tests/test_pr1366_finalize_thinking_card_guard.py tests/test_issue3479_ios_stream_scroll_jump.py tests/test_live_anchor_progress_echo.py
    • 109 passed
  • node --check static/ui.js
  • Claude Code review was run after implementation; one valid coverage concern was addressed with a Node-backed behavioral regression, and the final blocker-only review reported no blockers.

Risks / Follow-ups

Model Used

OpenAI GPT-5 coordinator with a delegated GPT-5.5 implementation worker. Claude Code CLI was used for independent read-only review of the diff.

@greptile-apps

greptile-apps Bot commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a Transparent Streaming live anchor-scene render path so that in-progress session data from runtime_journal_snapshot.anchor_activity_scene is displayed during live turns — bringing parity with Compact Worklog, which already consumed this snapshot. The fix dispatches from renderLiveAnchorActivityScene into a new _renderLiveAnchorActivitySceneTransparent function before the compact-only gate, reusing existing transparent node builders and adding new DOM attributes (data-anchor-live-scene-row, data-live-stream-owned) to identify live-owned rows.

  • _anchorSceneTransparentNodeForRow is made mode-aware: data-anchor-settled-scene-row is now conditional on opts.settled, while live rows get data-anchor-live-scene-row and data-live-stream-owned for targeted clearing on re-render.
  • _renderLiveAnchorActivitySceneForStream overrides the caller-supplied mode to 'transparent_stream' when isTransparentStream() is true, ensuring all four SSE-guard call sites (appendLiveToolCard, appendThinking, appendLiveCompressionCard, ensureLiveWorklogShell) project the correct scene type for transparent streams.
  • A DOM-executed Node.js regression test validates idempotent rendering, legacy-row hiding, and isolation of the settled marker from live rows.

Confidence Score: 4/5

The change is behaviorally correct for the normal live-anchor-scene flow, but the transparent renderer's return value differs from the compact path in edge cases that affect downstream control flow in ensureLiveWorklogShell.

The core render logic is sound and well-tested for the happy path. The open questions from the prior review round — wrote vs unconditional-true return value and the conditional _syncTransparentEventControls call — are still present in the code and affect the ensureLiveWorklogShell fallthrough when every row in a non-empty scene produces a null node. Those paths are not covered by the new Node.js regression, so a production scenario (e.g., a thinking-only scene with _showThinking disabled) could leave the DOM cleared without controls re-synced.

static/ui.js — specifically _renderLiveAnchorActivitySceneTransparent and its interaction with ensureLiveWorklogShell when all anchor-scene rows produce null nodes.

Important Files Changed

Filename Overview
static/ui.js Adds _renderLiveAnchorActivitySceneTransparent, dispatches from renderLiveAnchorActivityScene, makes data-anchor-settled-scene-row conditional on settled flag, and updates _renderLiveAnchorActivitySceneForStream to override mode for transparent streams. The return-value asymmetry (wrote vs unconditional true) and the conditional _syncTransparentEventControls call remain from the prior review round.
tests/test_live_to_final_anchor_visible_order.py Adds two new tests: a structural assertion test confirming the transparent gate precedes the compact gate, and a DOM-executed Node.js regression covering idempotency, legacy row hiding, settled/live marker isolation, and compact fallback after transparent render.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant SSE as SSE/Reattach
    participant rLAAS as renderLiveAnchorActivityScene
    participant rTrans as _renderLiveAnchorActivitySceneTransparent
    participant rComp as compact path
    participant DOM as DOM (liveAssistantTurn / blocks)

    SSE->>rLAAS: streamId, scene, opts
    rLAAS->>rLAAS: isTransparentStream()?

    alt Transparent Stream (NEW path)
        rLAAS->>rTrans: streamId, scene, opts
        rTrans->>rTrans: guard: session / streamId / rows.length
        rTrans->>DOM: remove [data-anchor-scene-row], legacy live rows
        rTrans->>DOM: hide [data-live-assistant] segments as anchors
        loop for each row in scene
            rTrans->>DOM: "_anchorSceneTransparentNodeForRow(row, {live:true})"
            DOM-->>rTrans: "node | null"
            rTrans->>DOM: insertBefore(liveRunStatus) / appendChild
        end
        rTrans->>rTrans: if(wrote) _syncTransparentEventControls(turn)
        rTrans-->>rLAAS: wrote (bool)
    else Compact Worklog path (existing)
        rLAAS->>rLAAS: !isCompactWorklogMode? return false
        rLAAS->>DOM: _anchorSceneWorklogGroup + _renderAnchorSceneRowsIntoWorklog
        rLAAS-->>SSE: true (unconditional)
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant SSE as SSE/Reattach
    participant rLAAS as renderLiveAnchorActivityScene
    participant rTrans as _renderLiveAnchorActivitySceneTransparent
    participant rComp as compact path
    participant DOM as DOM (liveAssistantTurn / blocks)

    SSE->>rLAAS: streamId, scene, opts
    rLAAS->>rLAAS: isTransparentStream()?

    alt Transparent Stream (NEW path)
        rLAAS->>rTrans: streamId, scene, opts
        rTrans->>rTrans: guard: session / streamId / rows.length
        rTrans->>DOM: remove [data-anchor-scene-row], legacy live rows
        rTrans->>DOM: hide [data-live-assistant] segments as anchors
        loop for each row in scene
            rTrans->>DOM: "_anchorSceneTransparentNodeForRow(row, {live:true})"
            DOM-->>rTrans: "node | null"
            rTrans->>DOM: insertBefore(liveRunStatus) / appendChild
        end
        rTrans->>rTrans: if(wrote) _syncTransparentEventControls(turn)
        rTrans-->>rLAAS: wrote (bool)
    else Compact Worklog path (existing)
        rLAAS->>rLAAS: !isCompactWorklogMode? return false
        rLAAS->>DOM: _anchorSceneWorklogGroup + _renderAnchorSceneRowsIntoWorklog
        rLAAS-->>SSE: true (unconditional)
    end
Loading

Reviews (4): Last reviewed commit: "Merge branch 'master' into franksong2702..." | Re-trigger Greptile

Comment thread static/ui.js
Comment thread static/ui.js
else blocks.appendChild(node);
wrote=true;
}
if(wrote) _syncTransparentEventControls(turn);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 _syncTransparentEventControls skipped after DOM clear when no rows are written

_syncTransparentEventControls is guarded by if(wrote), but the DOM-clearing sweeps above (removing [data-anchor-scene-row="1"], legacy live rows, [data-live-stream-owned="1"], etc.) run unconditionally once the rows-length guard is passed. If every row in a non-empty scene produces null from _anchorSceneTransparentNodeForRow — e.g. a scene containing only thinking rows when window._showThinking===false — the function clears all visible live content but leaves the turn's expand/collapse and copy-button controls wired to nodes that no longer exist. The compact path avoids this by not gating its equivalent sync call on whether content was written.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

🔬 Gate certification — GREEN ✅

Certified head: sha:0c5e2f0c7a7d (rebased e2dbc9cd) · PR: #5257 · franksong2702 — "Fix Transparent Streaming live anchor scene replay" · crit=2
Verdict: Clean fix for live-activity anchor-scene rendering during transparent streaming — transparent live rows now render into the live assistant turn before the compact gate, idempotently replacing legacy live-activity surfaces as the source of truth. Codex SAFE, suite green. Ready to merge.

What I ran (isolated worktree /tmp/wt-gate-5257, rebased onto current master)

Gate Result
Codex (reproduce) SAFE TO SHIP (no findings — ordering/idempotency/stream-ownership/no-jump all correct)
Full pytest suite (-p no:xdist, rebased) 11163 passed, 0 failed
PR's tests (test_live_to_final_anchor_visible_order, +2 new) 59 passed
node --check static/ui.js OK

Findings (clean)

Concept: 4/5 — fixes visible live-activity rendering glitches during transparent streaming (anchor-scene replay ordering); existing-flow streaming reliability.

Recommendation to the next agent

Ready to merge — cert fresh for sha:0c5e2f0c7a7d. crit=2 transparent-streaming anchor-scene-replay fix; Codex SAFE, full suite green, 2 new ordering/idempotency tests (59 in file), correct live→final ordering, idempotent legacy-row replacement, stream-id ownership intact, scroll-rebuild guard (no #5177/#5189 jump). Streaming-render surface but logic-verified by the tests + Codex. Rebases cleanly. Concept 4/5. Cert valid only at sha:0c5e2f0c7a7d.


Gate-certifier layer (warm-up → gate → release). I do not merge/tag/deploy/close. Cert valid only at sha:0c5e2f0c7a7d; a new push invalidates it → re-gate.

@nesquena-hermes nesquena-hermes added the gate-pass Full gate passed (Codex+Opus+suite+browser); queued Tier 1 for release agent label Jun 30, 2026
Co-Authored-By: Paperclip <noreply@paperclip.ing>
@franksong2702
franksong2702 force-pushed the franksong2702/fix-transparent-live-snapshot branch from 0c5e2f0 to 6ac5b9d Compare June 30, 2026 04:28
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

↻ Cert re-confirmed at current head 6ac5b9db (re-pushed via master-merge; now fresh-based on master). The ui.js anchor-scene-replay feature code is byte-identical to my prior GREEN cert (0 delta; only an 11-line test adjustment). Re-ran the gate on the freshened base: Codex SAFE (no reverted-master-fix / conflict-residue from the merge) + full suite 11165/0. Still ✅ GREEN — ready to merge at 6ac5b9db.

…o franksong2702/fix-transparent-live-snapshot

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refreshed again against current master.

  • New head: 0369e1e9d
  • Base merged: 1f85da54e
  • Local verification:
    • node --check static/ui.js
    • ./scripts/test.sh tests/test_live_to_final_anchor_visible_order.py (59 passed)

CI has started for the refreshed head; leaving this in CI/review monitoring.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refreshed again against current master.

  • New head: 7e038aaad
  • Base merged: 7c96296b
  • Local verification:
    • node --check static/ui.js
    • ./scripts/test.sh tests/test_live_to_final_anchor_visible_order.py (59 passed)

CI has started for the refreshed head; leaving this in CI/review monitoring.

@nesquena-hermes nesquena-hermes closed this pull request by merging all changes into nesquena:master in 8d5ff4c Jun 30, 2026
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in v0.51.765 (Wave 1 batch, via release PR #5277). Thanks @franksong2702! Combined-stage Codex SAFE + full pytest suite green + ESLint/ruff gates clean. 🎉

franksong2702 pushed a commit to franksong2702/hermes-webui-fork that referenced this pull request Jun 30, 2026
franksong2702 pushed a commit to franksong2702/hermes-webui-fork that referenced this pull request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gate-pass Full gate passed (Codex+Opus+suite+browser); queued Tier 1 for release agent size:L Large PR (>10 files or >250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants