Skip to content

Add Approval Navigation Banner and Related Functionality - #474

Open
govindavashishtha wants to merge 5 commits into
mainfrom
chore/FRONTEN-2362/trueforge-subagent-approval-flow-for-multiple-approvals-composer-it
Open

Add Approval Navigation Banner and Related Functionality#474
govindavashishtha wants to merge 5 commits into
mainfrom
chore/FRONTEN-2362/trueforge-subagent-approval-flow-for-multiple-approvals-composer-it

Conversation

@govindavashishtha

@govindavashishtha govindavashishtha commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a composer approval navigation banner for threads with one or more pending tool approvals, so users can find, cycle through, and re-focus approvals — including nested subagent tools — without losing context after scrolling.

  • New overridable ApprovalNavBanner slot + ApprovalNavContainer, wired above the composer when approvals are pending
  • Banner shows count (N tools need your input), (current/total), and prev/next chevrons (no wrap)
  • Clicking the banner scrolls back to the currently selected approval (expand ancestors → scroll → flash)
  • ApprovalFocusProvider registers approval DOM targets / subagent expand callbacks and handles focus + flash
  • Composer pause view updated so pending approvals keep the input paused while the banner is active
  • Changeset: @truefoundry/trueforge-ui minor
Screen.Recording.2026-08-27.at.7.11.08.PM.mov

Test plan

  • Single pending approval: banner shows singular copy; chevrons disabled; click banner scrolls/flashes that card
  • Multiple pending approvals: prev/next moves focus without wrapping; counter updates; allow/deny advances correctly
  • Nested subagent approval: focusing expands ancestor tool calls, then scrolls/flashes the nested approval
  • Scroll away from the focused approval, then click the banner (not chevrons) → returns to the same current item
  • After resolving all approvals, banner disappears and composer unpauses
  • Slot override still works for ApprovalNavBanner
  • pnpm --filter @truefoundry/trueforge-ui test passes

Note

Medium Risk
Changes composer pause ordering and thread-wide focus/scroll behavior for human-in-the-loop tool approvals, including nested sub-agent DOM timing with retry logic.

Overview
Adds an approval navigation banner above the composer when one or more tool approvals are pending, with an overridable ApprovalNavBanner slot and ApprovalNavContainer.

While approvals are open, useComposerPauseView returns { kind: 'approval' } (below MCP/custom/ask-user): the banner shows “N tools need your input”, a (current/total) counter, and prev/next chevrons that do not wrap; the message input stays disabled and visually flush with the banner. useApprovalNav drives selection from useTrueFoundryApprovals, auto-focuses the first item, and keeps the same list index as items are resolved.

ApprovalFocusProvider (on the thread) registers scroll targets on ToolCallCard via approvalId, expands nested create_sub_agent ancestors (findSubAgentAncestorsForApproval), smooth-scrolls, and applies a short flash highlight. Tool call rendering passes approvalId through sandbox/MCP/generic paths and registers sub-agent expand callbacks.

Theme adds approval-banner tokens and aui-approval-flash animation; public exports include ApprovalNavBanner, useApprovalNav, and useTrueFoundryApprovals.

Reviewed by Cursor Bugbot for commit e50e635. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e50e635

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@truefoundry/trueforge-ui Minor
frontend Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/trueforge-ui/src/containers/ToolCallContainer.tsx
Comment thread packages/trueforge-ui/src/containers/approvalFocus.tsx
Comment thread packages/trueforge-ui/src/hooks/useApprovalNav.ts
Comment thread .changeset/pre/approval-nav-banner.md
@kumarsks619

Copy link
Copy Markdown
Contributor

should add some padding to the highlighting box @govindavashishtha

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e50e635. Configure here.

flashingApprovalId,
}),
[registerTarget, registerExpand, focus, flashingApprovalId],
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Focus context churns on stream

Medium Severity

ApprovalFocusProvider reads thread.messages into the focus callback, so the context value is recreated on every message update. Every ToolCallCard subscribes via useOptionalApprovalFocus, so streaming tokens re-render all tool cards — including expanded Monaco/markdown bodies — even when no approval is pending.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e50e635. Configure here.

}
}
}, MOUNT_RETRY_MS);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale ancestors block nested focus

Medium Severity

focus snapshots subagent ancestors once from messages at call time, and useApprovalNav then records focusedIdRef so the same id is never focused again. If useTrueFoundryApprovals updates before the thread tree contains the nested tool, retries expand nothing, and a later message update does not re-focus.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e50e635. Configure here.

Comment thread .changeset/pre/approval-nav-banner.md
aria-label={`${label}. Click to go to approval ${String(current)} of ${String(count)}.`}
onClick={onFocusCurrent}
className={cn(
// Figma light 6747:4232 / dark 6748:1977 — top border only; sides & bottom open into the composer.

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.

can remove comment, not useful

onFocusCurrent,
className,
}: ApprovalNavBannerProps) {
// Figma Agents node 6747:4232 — "N tools need your input"

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.

same

import { cn } from './lib/cn.js';

export type ApprovalNavBannerProps = {
count: number;

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.

Suggested change
count: number;
total: number;

Comment on lines +89 to +90
// Pending-approval cards keep top/right padding always so flash doesn't jump the layout.
// Left stays unpadded so the step rail stays aligned with siblings.

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.

can remove

toolName?: string;
argsText?: string;
/** Kept for call-site compat; flash/scroll target is `ToolCallCard`. */
approvalId?: string;

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.

couldn't get it why we need this prop if not being used

export function useRegisterApprovalTarget(approvalId: string | undefined, getElement: () => HTMLElement | null): void {
const api = useOptionalApprovalFocus();
const getElementRef = useRef(getElement);
getElementRef.current = getElement;

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.

shouldn't be part of render cycle, should do it a useeffect

export function useRegisterApprovalExpand(toolCallId: string, expand: () => void): void {
const api = useOptionalApprovalFocus();
const expandRef = useRef(expand);
expandRef.current = expand;

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.

same

}

/** Registers a sub-agent expand callback. No-ops outside the provider (tests). */
export function useRegisterApprovalExpand(toolCallId: string, expand: () => void): void {

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.

i think this unused

}

/** Registers a DOM target for scroll/flash. No-ops outside the provider (tests). */
export function useRegisterApprovalTarget(approvalId: string | undefined, getElement: () => HTMLElement | null): void {

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.

want to understand how it is working, whats the flow here

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.

2 participants