Skip to content

feat(web): group consecutive tool-use cards#604

Open
junxin367 wants to merge 7 commits intotiann:mainfrom
junxin367:feat/web-tool-use-grouping
Open

feat(web): group consecutive tool-use cards#604
junxin367 wants to merge 7 commits intotiann:mainfrom
junxin367:feat/web-tool-use-grouping

Conversation

@junxin367
Copy link
Copy Markdown
Contributor

Summary

  • group consecutive root-level execution tool cards in web chat into one collapsible card
  • keep approval / AskUserQuestion / request_user_input cards standalone and use them as grouping boundaries
  • auto-load older pages when expanding an oldest incomplete group, and add regression coverage for grouping/runtime/thread behavior

Reason

  • reduce chat noise from tool-heavy sessions while preserving full timeline access and detail drill-down

Scope

  • web-only visible projection and grouped tool card UI
  • no hub API, persistence, or pagination semantic changes

Test

  • cd web && bun run test -- src/chat/toolGroups.test.ts src/components/ToolCard/ToolGroupCard.test.tsx src/components/AssistantChat/HappyThread.test.tsx
  • cd web && bun run typecheck

Risk / Rollback

  • Risk: grouped rendering boundaries and oldest-history hydration behavior in session chat
  • Rollback: revert this PR to restore per-tool card rendering

Links

  • none

Reviewer Notes

  • focus on grouping eligibility and summary extraction in web/src/chat/toolGroups.ts
  • focus on incomplete-history hydration and stable open-state behavior in ToolGroupCard and HappyThread
  • UI-only change; no browser screenshot captured in this CLI pass

Add a web-only visible projection that groups consecutive root-level execution tools into expandable cards.
Keep approval and question tools standalone, reuse older-history loading on expand, and add regression coverage for grouping and UI behavior.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review mode: initial

Findings

  • [Major] needsOlderHistory is only set when the grouped run starts at index 0, so a tool group that is the oldest visible group but follows a text/event block never auto-loads older pages when expanded. That breaks the promised oldest-incomplete-group hydration on pages that begin with non-tool content. web/src/chat/toolGroups.ts:254-265
    Suggested fix:
    let sawGroup = false
    
    for (let index = 0; index < blocks.length; index += 1) {
        ...
        const needsOlderHistory = options.hasMoreMessages && !sawGroup
        sawGroup = true
        ...
    }

Summary

  • Review mode: initial. One blocking behavior gap found; no other issues confirmed in the diff.

Testing

  • Not run (bun is unavailable in this environment).

HAPI Bot

Comment thread web/src/chat/toolGroups.ts Outdated
Mark needsOlderHistory on the first visible grouped tool run even when earlier visible blocks are non-tool content, and add regression coverage for the boundary.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review mode: follow-up after new commits

Findings

  • [Major] Incomplete tool groups can stop hydrating after the first older-page request. ToolGroupCard starts a load, but the same effect depends on ctx.isLoadingMoreMessages; when the shared loader flips that flag, React runs the cleanup and marks the in-flight request as cancelled. The eventual promise resolution then cannot clear isHydratingHistory, and the next effect pass returns early on isHydratingHistory, so a multi-page incomplete group can stay stuck instead of loading until its boundary is complete. Evidence: web/src/components/ToolCard/ToolGroupCard.tsx:174.
    Suggested fix:
    useEffect(() => {
        if (!open || !props.block.needsOlderHistory || historyExhausted) return
        if (!ctx.hasMoreMessages) {
            setHistoryExhausted(true)
            return
        }
    
        let cancelled = false
        setIsHydratingHistory(true)
        void ctx.loadOlderMessagesPreservingScroll()
            .then((loaded) => {
                if (cancelled) return
                setIsHydratingHistory(false)
                if (!loaded) setHistoryExhausted(true)
            })
            .catch(() => {
                if (cancelled) return
                setIsHydratingHistory(false)
                setHistoryExhausted(true)
            })
    
        return () => {
            cancelled = true
        }
    }, [
        open,
        props.block,
        ctx.hasMoreMessages,
        ctx.loadOlderMessagesPreservingScroll,
        historyExhausted,
    ])

Summary

  • Review mode: follow-up after new commits. The prior bot finding about marking the oldest visible group appears addressed, but grouped expansion still has a remaining hydration regression for incomplete history.

Testing

  • Not run (automation; bun is unavailable in this environment: /bin/bash: bun: command not found).

HAPI Bot

Comment thread web/src/components/ToolCard/ToolGroupCard.tsx Outdated
junxin367 added 2 commits May 9, 2026 11:31
Decouple ToolGroupCard older-history chaining from the shared loading flag, invalidate stale hydration runs safely, and add regression coverage for multi-page hydration.
…uping

# Conflicts:
#	web/src/components/ToolCard/ToolCard.tsx
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review mode: follow-up after new commits

Findings

  • [Major] ToolGroupCard treats any false from loadOlderMessagesPreservingScroll() as exhausted history. That includes transient isLoadingMoreMessages / initial-settling returns, so if a user expands an incomplete group while the thread is already paginating elsewhere, the card can mark itself exhausted and stop retrying after the in-flight load completes. Evidence web/src/components/ToolCard/ToolGroupCard.tsx:192.
  • [Major] isInteractiveToolBlock() excludes every tool that carries a permission object, not just pending approval prompts. Approved or denied execution tools therefore never enter a group, which defeats the new collapsing behavior for common permissioned shell/edit calls. Evidence web/src/chat/toolGroups.ts:188.

Summary
Review mode: follow-up after new commits. The prior hydration-stall issue appears addressed, but the new grouping still has two regressions: a transient-load race in incomplete-group auto-hydration and an overly broad permission exclusion that keeps approved execution tools out of groups.

Testing
Not run (automation; bun unavailable in this environment).

Comment thread web/src/components/ToolCard/ToolGroupCard.tsx
Comment thread web/src/chat/toolGroups.ts
- retry incomplete group hydration after transient pagination contention\n- keep approved and denied permissioned tool cards eligible for grouping\n- cover both regressions with targeted web tests
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review mode: follow-up after new commits

Findings

  • [Major] Completed Codex permission cards can be collapsed into execution groups, so approval/denial milestones stop acting as standalone timeline boundaries. CodexPermission is emitted for Codex approval prompts (cli/src/codex/codexRemoteLauncher.ts:376) and has a dedicated permission presentation (web/src/components/ToolCard/knownTools.tsx:178), but the new grouping predicate only excludes pending permissions plus AskUserQuestion/request_user_input. Once the permission completes, block.tool.permission?.status is no longer pending, CodexPermission falls through isEligibleForToolGrouping, and consecutive permission + command cards can be hidden inside a collapsed group by default. Evidence: web/src/chat/toolGroups.ts:198.
    Suggested fix:
    const INTERACTIVE_TOOL_NAMES = new Set([
        'CodexPermission',
    ])
    
    function isInteractiveToolBlock(block: ToolCallBlock): boolean {
        return INTERACTIVE_TOOL_NAMES.has(block.tool.name)
            || block.tool.permission?.status === 'pending'
            || isAskUserQuestionToolName(block.tool.name)
            || isRequestUserInputToolName(block.tool.name)
    }

Summary
Review mode: follow-up after new commits. The previous hydration issue appears addressed, but the latest grouping predicate still lets completed Codex permission cards lose their standalone boundary.

Testing
Not run (automation; review only, and bun is not installed in this environment).

HAPI Bot

Comment thread web/src/chat/toolGroups.ts
- treat CodexPermission as a semantic grouping boundary even after approval\n- keep permissioned execution tools groupable while preserving permission milestones\n- add regression coverage for Codex permission eligibility and boundary behavior
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Findings

  • [Major] Only groups that start at the oldest visible boundary should be marked incomplete. The current !sawVisibleGroup check also marks the first grouped run after visible text, user messages, permissions, or a single standalone tool as needsOlderHistory; expanding that complete group then calls loadOlderMessagesPreservingScroll() and may keep pulling unrelated older pages until some earlier group appears or history is exhausted. Evidence web/src/chat/toolGroups.ts:260.
    Suggested fix:
    const startsAtOldestVisibleBoundary = visibleBlocks.length === 0
    const needsOlderHistory = options.hasMoreMessages && startsAtOldestVisibleBoundary

Summary
Review mode: follow-up after new commits. The prior Codex permission grouping issue is fixed, but the incomplete-history marker is still too broad and can trigger unnecessary auto-pagination from complete groups.

Testing
Not run (automation; review only, and PR code was not executed).

HAPI Bot

Comment thread web/src/chat/toolGroups.ts Outdated
- only mark groups at the oldest visible boundary as needing older history\n- avoid auto-paginating complete groups behind text, standalone tools, or permission milestones\n- add regression coverage for the adjacent boundary cases
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Findings
No issues found.

Summary
Review mode: follow-up after new commits. The prior incomplete-history marker finding is addressed in web/src/chat/toolGroups.ts, and I did not find any new correctness, security, data-loss, performance, or maintainability issue in the latest diff. Residual risk: UI behavior was reviewed statically only; grouped-card browser smoke coverage is still dependent on CI/manual validation.

Testing
Not run (automation; bun is not installed in this runner).

HAPI Bot

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