Skip to content

fix(webui): link approval card to global auto-approve settings - #5247

Merged
think-in-universe merged 17 commits into
mainfrom
issue-5246-global-auto-approve-link
Jul 1, 2026
Merged

think-in-universe merged 17 commits into
mainfrom
issue-5246-global-auto-approve-link

Conversation

@italic-jinxin

@italic-jinxin italic-jinxin commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds a discoverability link under the approval card's per-tool "always allow" checkbox when global auto-approve is disabled.
  • Routes the link to Settings > Tools so users can quickly find the global setting for automatically approving and executing all actions.
  • Reads agent.auto_approve_tools only while an approval gate is visible and keeps existing per-tool approval behavior unchanged.
  • Adds approval card coverage for showing the settings link when global auto-approve is off and hiding it when enabled.

Linked Issue

Closes #5246

Screenshots

yolo prompt in dialog

Validation

  • node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.test.mjs
  • node --test --test-name-pattern 'Chat deny gate callback routes through approve compatibility path' crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs
  • npm run build
  • git diff --check

Security Impact

No permission model changes. This only exposes a shortcut to an existing Tools setting and preserves the existing approval flow.

Database Impact

No schema or migration changes.

Blast Radius

Limited to WebUI v2 approval card rendering, settings-state lookup while an approval gate is visible, and the bundled static frontend asset.

Rollback Plan

Revert this PR to remove the approval-card shortcut and return the card to the previous per-tool-only approval UI.

@italic-jinxin italic-jinxin added size: L 200-499 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jun 25, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 25, 2026 14:38 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 77093bc0-aad4-4c88-bc6d-c21cde4798c1

📥 Commits

Reviewing files that changed from the base of the PR and between 2f1a746 and b2824bb.

📒 Files selected for processing (2)
  • crates/ironclaw_product_workflow/src/reborn_services.rs
  • crates/ironclaw_webui_v2/src/handlers.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a conditional “Automatically approve and execute all actions” link in the chat approval flow, routing to settings when available.
    • The session and chat UI now carry the related feature flag so the approval card can show/hide the shortcut.
    • Added localized text for the new approval link across multiple languages.
  • Bug Fixes

    • Improved Enter-key sending behavior by respecting the current DOM disabled state more reliably.

Walkthrough

Backend session bootstrap now exposes features.global_auto_approve, the WebUI threads it into chat approval rendering, and ApprovalCard shows a settings link when always-approve is available but not enabled globally. ChatInput also blocks sends when the DOM marks the textarea as disabled.

Changes

Global auto-approve shortcut

Layer / File(s) Summary
Session feature flag
crates/ironclaw_product_workflow/src/reborn_services.rs, crates/ironclaw_webui_v2/src/handlers.rs, crates/ironclaw_webui_v2/tests/webui_v2_handlers_contract.rs, crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
Adds global_auto_approve_enabled to RebornServicesApi/RebornServices, surfaces WebUiV2Features.global_auto_approve in the session response with timeout and fallback behavior, and adds contract coverage for the facade read and session output.
Auth and layout propagation
crates/ironclaw_webui_v2_static/static/js/app/auth.js, crates/ironclaw_webui_v2_static/static/js/app/app.js, crates/ironclaw_webui_v2_static/static/js/layout/gateway-layout.js, crates/ironclaw_webui_v2_static/static/js/pages/chat/chat-page.js, crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js, crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs, crates/ironclaw_webui_v2_static/src/assets.rs
Derives globalAutoApproveEnabled from session state and carries it through authenticated layout, outlet context, chat page, and chat rendering, with matching test and asset assertions.
Approval card link and i18n
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js, crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.test.mjs, crates/ironclaw_webui_v2_static/static/js/i18n/*.js
ApprovalCard conditionally renders a Link to /settings/tools when gate.allowAlways is true and globalAutoApproveEnabled is false, and the new label text is added across the locale packs with updated component tests.
Estimated code review effort: 3 (Moderate) ~25 minutes

Chat Input DOM Guard

Layer / File(s) Summary
Send-disabled DOM guard
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js, crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjs
handleSend and the Enter-key path now check dataset.sendDisabled from the textarea ref or event target before submitting, and the test asserts Enter-send is blocked from current DOM state.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning chat-input.js changes send-disable handling, which is unrelated to the global auto-approve shortcut objective. Remove the chat-input send-blocking change from this PR or split it into a separate change set.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Conventional-commit style title matches the approval shortcut change and the webui scope.
Description check ✅ Passed The description covers summary, linked issue, validation, security, DB, blast radius, and rollback, though some template sections are missing.
Linked Issues check ✅ Passed [#5246] The PR adds the approval-card link, shows it only when global auto-approve is off, routes to Tools, and keeps per-tool approval behavior unchanged.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

gemini-code-assist[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js`:
- Around line 85-93: The global auto-approve flag is being derived from the
settings API response instead of the bootstrap globals, which breaks the
frontend flag-source contract. Update the chat page logic in the
`settingsQuery`/`globalAutoApproveEnabled` path to read the flag from the
existing bootstrap globals source used by the web UI, and remove the dependency
on `settingsQuery.data.settings` for this value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4c1ede1e-fc8e-4d24-972f-bc90fc073f43

📥 Commits

Reviewing files that changed from the base of the PR and between a38119f and 88398ee.

⛔ Files ignored due to path filters (1)
  • crates/ironclaw_webui_v2_static/static/dist/app.js is excluded by !**/dist/**
📒 Files selected for processing (5)
  • crates/ironclaw_webui_v2_static/static/js/i18n/en.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs

Comment thread crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js Outdated
# Conflicts:
#	crates/ironclaw_webui_v2_static/static/dist/app.js
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 25, 2026 15:08 Destroyed

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_webui_v2/src/handlers.rs`:
- Around line 121-133: The `global_auto_approve_enabled` helper is swallowing an
operator-config read error by using `let Ok(config) = ... else { return false
}`, which hides a boundary/IO failure. Keep the fail-closed `false` fallback,
but make it explicit by adding an inline `// silent-ok: ...` justification near
the `get_operator_config_key` call, or otherwise log/propagate the `Err` before
returning. Use `global_auto_approve_enabled`, `get_operator_config_key`, and
`AUTO_APPROVE_CONFIG_KEY` to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 27468db3-7a5e-4053-a0b9-5f400658da42

📥 Commits

Reviewing files that changed from the base of the PR and between 88398ee and 81ba09b.

⛔ Files ignored due to path filters (1)
  • crates/ironclaw_webui_v2_static/static/dist/app.js is excluded by !**/dist/**
📒 Files selected for processing (9)
  • crates/ironclaw_webui_v2/src/handlers.rs
  • crates/ironclaw_webui_v2/tests/webui_v2_handlers_contract.rs
  • crates/ironclaw_webui_v2_static/src/assets.rs
  • crates/ironclaw_webui_v2_static/static/js/app/app.js
  • crates/ironclaw_webui_v2_static/static/js/app/auth.js
  • crates/ironclaw_webui_v2_static/static/js/layout/gateway-layout.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/chat-page.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs

Comment thread crates/ironclaw_webui_v2/src/handlers.rs
@railway-app

railway-app Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5247 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 1, 2026 at 9:39 am

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 25, 2026 15:37 Destroyed
@think-in-universe

Copy link
Copy Markdown
Collaborator

@claude review

@claude

This comment was marked as resolved.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 25, 2026 16:01 Destroyed
@italic-jinxin italic-jinxin self-assigned this Jun 25, 2026
# Conflicts:
#	crates/ironclaw_webui_v2_static/static/dist/app.js
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 25, 2026 16:12 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 25, 2026 16:25 Destroyed
@think-in-universe

Copy link
Copy Markdown
Collaborator

[BLOCK] Not ready for human final review.

Blocking status:

  • Reborn E2E/WebUI v2 smoke tests report multiple failing jobs on this head.
  • require_ci_green is enabled, so we cannot mark this PR as ready until CI is green.

No new inline findings were added in this pass.

Guidance for human follow-up:

  • Resolve the current failing suites (especially approvals/session/bootstrap interactions) and re-run the failing WebUI / Reborn integration tests.
  • Recheck that global auto-approve value propagation remains stable from session bootstrap through chat rendering.

@think-in-universe

Copy link
Copy Markdown
Collaborator

@claude review

@think-in-universe

Copy link
Copy Markdown
Collaborator

Mergeability check: is (). Please rebase or merge the current base branch to resolve conflicts before continuing review.

@think-in-universe

Copy link
Copy Markdown
Collaborator

Mergeability check: mergeStateStatus is DIRTY and mergeable is CONFLICTING. Please rebase or merge the current base branch to resolve conflicts before review can proceed.

@github-actions github-actions Bot added the size: L 200-499 changed lines label Jun 29, 2026
# Conflicts:
#	crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 29, 2026 15:08 Destroyed
# Conflicts:
#	crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.test.mjs
#	crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 June 30, 2026 06:27 Destroyed

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js (2)

171-196: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard the staging failure path with the same draft/session snapshot.

Lines 194-195 skip the activeDraftContextRef check that the success path already applies. If stageFiles() rejects after a thread or auth switch, the old failure is rendered in the newly active composer. That breaks the file’s own “composer can stay mounted across a token/session switch” invariant.

Suggested fix
         .catch(() => {
+          const current = activeDraftContextRef.current;
+          if (
+            current.draftKey !== expectedDraftKey ||
+            current.storageScope !== expectedStorageScope ||
+            authScope() !== expectedStorageScope
+          ) {
+            return;
+          }
           setAttachmentError(t("chat.attachmentStagingFailed"));
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`
around lines 171 - 196, The staging failure path in chat-input.js should use the
same draft/session guard as the success path. In the staging promise chain
around stageFiles and activeDraftContextRef, capture the current
draftKey/storageScope snapshot before awaiting, and in the catch handler only
call setAttachmentError if the activeDraftContextRef still matches that snapshot
and authScope() is unchanged. Keep the existing success-path check in place so
stale staging errors cannot overwrite the newly active composer state.

226-272: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Snapshot the active composer before awaiting onSend().

handleSend() now defends the pre-send path, but Lines 245-262 still apply success cleanup to whatever composer is mounted when the promise resolves. If the user switches thread or auth scope while the send is in flight, the stale continuation will clear the next composer’s text/attachments via setText("") and setAttachments([]). That is draft loss, not just a visual glitch.

Suggested fix
   const handleSend = React.useCallback(async () => {
+    const expectedDraftKey = draftKey;
+    const expectedStorageScope = storageScope;
     const trimmed = text.trim();
     const hasAttachments = attachments.length > 0;
     const sendContent = trimmed || (hasAttachments ? ATTACHMENTS_ONLY_CONTENT : "");
@@
     try {
       const response = await onSend(sendContent, {
         attachments,
         displayContent: trimmed,
       });
       if (response === null) return;
+      const current = activeDraftContextRef.current;
+      if (
+        current.draftKey !== expectedDraftKey ||
+        current.storageScope !== expectedStorageScope ||
+        authScope() !== expectedStorageScope
+      ) {
+        return;
+      }
       setText("");
       setAttachments([]);
       attachmentsRef.current = [];
       setAttachmentError("");
       cancelPendingDraft();
-      clearDraft(draftKey);
-      clearStagedAttachments(draftKey);
+      clearDraft(expectedDraftKey);
+      clearStagedAttachments(expectedDraftKey);
       if (textareaRef.current) textareaRef.current.style.height = "auto";
@@
   }, [
     text,
     attachments,
     disabled,
     sendDisabled,
     isSending,
     onSend,
     draftKey,
+    storageScope,
     cancelPendingDraft,
   ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`
around lines 226 - 272, handleSend currently applies post-send cleanup to
whichever composer is mounted when onSend resolves, which can wipe a different
thread/scope’s draft after a switch. Snapshot the active composer identity and
draft state before awaiting onSend in handleSend, then only run the success
cleanup path (setText, setAttachments, attachmentsRef, cancelPendingDraft,
clearDraft, clearStagedAttachments, height reset) if the same composer is still
active. Use the existing draftKey, textareaRef, and sendBlockedRef flow to guard
the continuation.
crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js (1)

127-153: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

composerSendDisabled in handleSend deps is redundant and defeats the ref pattern.

The callback reads composerSendBlockedRef.current precisely to avoid listing composerSendDisabled as a dep (keeping the callback stable across processing-state changes). Having it in deps anyway causes the callback to re-create on every processing tick, which re-triggers any downstream memoization depending on handleSend (e.g. SuggestionChips, ChatInput).

♻️ Remove the redundant dep
     [
       activeThreadId,
       activeThreadHasGate,
       approvalSubmitWarning,
-      composerSendDisabled,
       onSelectThread,
       send,
     ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js` around lines
127 - 153, The handleSend callback in chat.js is still depending on
composerSendDisabled even though it already uses composerSendBlockedRef.current
to avoid that state in the dependency list. Remove composerSendDisabled from the
useCallback dependency array for handleSend so the callback stays stable across
processing-state updates and does not force unnecessary re-renders in downstream
consumers like SuggestionChips and ChatInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js`:
- Around line 55-59: The resolving reset in approval-card should be keyed to a
stable request identifier instead of the gate object identity, because the
current React.useEffect in approval-card.js clears isResolvingRef and
isResolving whenever gate is re-created. Update the effect to depend on the
gate’s stable request id (or equivalent unique key used by the approve/deny
flow) so the in-flight guard in the approve/deny handlers stays active across
rerenders for the same request and only resets when the actual request changes.

---

Outside diff comments:
In `@crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js`:
- Around line 127-153: The handleSend callback in chat.js is still depending on
composerSendDisabled even though it already uses composerSendBlockedRef.current
to avoid that state in the dependency list. Remove composerSendDisabled from the
useCallback dependency array for handleSend so the callback stays stable across
processing-state updates and does not force unnecessary re-renders in downstream
consumers like SuggestionChips and ChatInput.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`:
- Around line 171-196: The staging failure path in chat-input.js should use the
same draft/session guard as the success path. In the staging promise chain
around stageFiles and activeDraftContextRef, capture the current
draftKey/storageScope snapshot before awaiting, and in the catch handler only
call setAttachmentError if the activeDraftContextRef still matches that snapshot
and authScope() is unchanged. Keep the existing success-path check in place so
stale staging errors cannot overwrite the newly active composer state.
- Around line 226-272: handleSend currently applies post-send cleanup to
whichever composer is mounted when onSend resolves, which can wipe a different
thread/scope’s draft after a switch. Snapshot the active composer identity and
draft state before awaiting onSend in handleSend, then only run the success
cleanup path (setText, setAttachments, attachmentsRef, cancelPendingDraft,
clearDraft, clearStagedAttachments, height reset) if the same composer is still
active. Use the existing draftKey, textareaRef, and sendBlockedRef flow to guard
the continuation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9e7e69da-822c-44ec-8c94-b9caaa6d7e3a

📥 Commits

Reviewing files that changed from the base of the PR and between 636fad0 and f50b25d.

📒 Files selected for processing (16)
  • crates/ironclaw_webui_v2_static/static/js/i18n/ar.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/de.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/en.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/es.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/fr.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/hi.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/ja.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/ko.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/pt-BR.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/uk.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/zh-CN.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs

@coderabbitai coderabbitai 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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js (2)

171-196: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard the staging failure path with the same draft/session snapshot.

Lines 194-195 skip the activeDraftContextRef check that the success path already applies. If stageFiles() rejects after a thread or auth switch, the old failure is rendered in the newly active composer. That breaks the file’s own “composer can stay mounted across a token/session switch” invariant.

Suggested fix
         .catch(() => {
+          const current = activeDraftContextRef.current;
+          if (
+            current.draftKey !== expectedDraftKey ||
+            current.storageScope !== expectedStorageScope ||
+            authScope() !== expectedStorageScope
+          ) {
+            return;
+          }
           setAttachmentError(t("chat.attachmentStagingFailed"));
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`
around lines 171 - 196, The staging failure path in chat-input.js should use the
same draft/session guard as the success path. In the staging promise chain
around stageFiles and activeDraftContextRef, capture the current
draftKey/storageScope snapshot before awaiting, and in the catch handler only
call setAttachmentError if the activeDraftContextRef still matches that snapshot
and authScope() is unchanged. Keep the existing success-path check in place so
stale staging errors cannot overwrite the newly active composer state.

226-272: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Snapshot the active composer before awaiting onSend().

handleSend() now defends the pre-send path, but Lines 245-262 still apply success cleanup to whatever composer is mounted when the promise resolves. If the user switches thread or auth scope while the send is in flight, the stale continuation will clear the next composer’s text/attachments via setText("") and setAttachments([]). That is draft loss, not just a visual glitch.

Suggested fix
   const handleSend = React.useCallback(async () => {
+    const expectedDraftKey = draftKey;
+    const expectedStorageScope = storageScope;
     const trimmed = text.trim();
     const hasAttachments = attachments.length > 0;
     const sendContent = trimmed || (hasAttachments ? ATTACHMENTS_ONLY_CONTENT : "");
@@
     try {
       const response = await onSend(sendContent, {
         attachments,
         displayContent: trimmed,
       });
       if (response === null) return;
+      const current = activeDraftContextRef.current;
+      if (
+        current.draftKey !== expectedDraftKey ||
+        current.storageScope !== expectedStorageScope ||
+        authScope() !== expectedStorageScope
+      ) {
+        return;
+      }
       setText("");
       setAttachments([]);
       attachmentsRef.current = [];
       setAttachmentError("");
       cancelPendingDraft();
-      clearDraft(draftKey);
-      clearStagedAttachments(draftKey);
+      clearDraft(expectedDraftKey);
+      clearStagedAttachments(expectedDraftKey);
       if (textareaRef.current) textareaRef.current.style.height = "auto";
@@
   }, [
     text,
     attachments,
     disabled,
     sendDisabled,
     isSending,
     onSend,
     draftKey,
+    storageScope,
     cancelPendingDraft,
   ]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`
around lines 226 - 272, handleSend currently applies post-send cleanup to
whichever composer is mounted when onSend resolves, which can wipe a different
thread/scope’s draft after a switch. Snapshot the active composer identity and
draft state before awaiting onSend in handleSend, then only run the success
cleanup path (setText, setAttachments, attachmentsRef, cancelPendingDraft,
clearDraft, clearStagedAttachments, height reset) if the same composer is still
active. Use the existing draftKey, textareaRef, and sendBlockedRef flow to guard
the continuation.
crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js (1)

127-153: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

composerSendDisabled in handleSend deps is redundant and defeats the ref pattern.

The callback reads composerSendBlockedRef.current precisely to avoid listing composerSendDisabled as a dep (keeping the callback stable across processing-state changes). Having it in deps anyway causes the callback to re-create on every processing tick, which re-triggers any downstream memoization depending on handleSend (e.g. SuggestionChips, ChatInput).

♻️ Remove the redundant dep
     [
       activeThreadId,
       activeThreadHasGate,
       approvalSubmitWarning,
-      composerSendDisabled,
       onSelectThread,
       send,
     ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js` around lines
127 - 153, The handleSend callback in chat.js is still depending on
composerSendDisabled even though it already uses composerSendBlockedRef.current
to avoid that state in the dependency list. Remove composerSendDisabled from the
useCallback dependency array for handleSend so the callback stays stable across
processing-state updates and does not force unnecessary re-renders in downstream
consumers like SuggestionChips and ChatInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js`:
- Around line 55-59: The resolving reset in approval-card should be keyed to a
stable request identifier instead of the gate object identity, because the
current React.useEffect in approval-card.js clears isResolvingRef and
isResolving whenever gate is re-created. Update the effect to depend on the
gate’s stable request id (or equivalent unique key used by the approve/deny
flow) so the in-flight guard in the approve/deny handlers stays active across
rerenders for the same request and only resets when the actual request changes.

---

Outside diff comments:
In `@crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js`:
- Around line 127-153: The handleSend callback in chat.js is still depending on
composerSendDisabled even though it already uses composerSendBlockedRef.current
to avoid that state in the dependency list. Remove composerSendDisabled from the
useCallback dependency array for handleSend so the callback stays stable across
processing-state updates and does not force unnecessary re-renders in downstream
consumers like SuggestionChips and ChatInput.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`:
- Around line 171-196: The staging failure path in chat-input.js should use the
same draft/session guard as the success path. In the staging promise chain
around stageFiles and activeDraftContextRef, capture the current
draftKey/storageScope snapshot before awaiting, and in the catch handler only
call setAttachmentError if the activeDraftContextRef still matches that snapshot
and authScope() is unchanged. Keep the existing success-path check in place so
stale staging errors cannot overwrite the newly active composer state.
- Around line 226-272: handleSend currently applies post-send cleanup to
whichever composer is mounted when onSend resolves, which can wipe a different
thread/scope’s draft after a switch. Snapshot the active composer identity and
draft state before awaiting onSend in handleSend, then only run the success
cleanup path (setText, setAttachments, attachmentsRef, cancelPendingDraft,
clearDraft, clearStagedAttachments, height reset) if the same composer is still
active. Use the existing draftKey, textareaRef, and sendBlockedRef flow to guard
the continuation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9e7e69da-822c-44ec-8c94-b9caaa6d7e3a

📥 Commits

Reviewing files that changed from the base of the PR and between 636fad0 and f50b25d.

📒 Files selected for processing (16)
  • crates/ironclaw_webui_v2_static/static/js/i18n/ar.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/de.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/en.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/es.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/fr.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/hi.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/ja.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/ko.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/pt-BR.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/uk.js
  • crates/ironclaw_webui_v2_static/static/js/i18n/zh-CN.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs
🛑 Comments failed to post (1)
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js (1)

55-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Key the resolving reset off a stable request id, not gate object identity.

Line 55 clears the in-flight guard on any new gate reference. If the parent re-derives the same pending gate during a rerender, Lines 71-84 stop protecting the approve/deny actions and the same request can be resolved twice.

Suggested fix
+  const gateResetKey = gate?.requestId ?? null;
+
   React.useEffect(() => {
     setExpandedPayload(false);
     isResolvingRef.current = false;
     setIsResolving(false);
-  }, [gate]);
+  }, [gateResetKey]);

Also applies to: 71-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/approval-card.js`
around lines 55 - 59, The resolving reset in approval-card should be keyed to a
stable request identifier instead of the gate object identity, because the
current React.useEffect in approval-card.js clears isResolvingRef and
isResolving whenever gate is re-created. Update the effect to depend on the
gate’s stable request id (or equivalent unique key used by the approve/deny
flow) so the in-flight guard in the approve/deny handlers stays active across
rerenders for the same request and only resets when the actual request changes.

@think-in-universe

Copy link
Copy Markdown
Collaborator

@claude review

@claude

This comment was marked as resolved.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 July 1, 2026 08:42 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 July 1, 2026 08:52 Destroyed
# Conflicts:
#	crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 July 1, 2026 09:10 Destroyed
@italic-jinxin

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

This comment was marked as resolved.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js (1)

242-253: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New DOM-dataset guard duplicates the existing ref-based guard.

domSendDisabled (reading dataset.sendDisabled) is derived from exactly the same disabled/sendDisabled inputs already folded into sendBlockedRef.current (sendBlocked = disabled || sendDisabled || isSending, set every render at Line 50-53) and into the explicit disabled/sendDisabled/isSending checks right next to it. Since handleSend's useCallback deps now include all three (Line 305-315), there's no stale-closure scenario this DOM read protects against — refs already solve that per the standard React pattern. The added test (chat-input.test.mjs 247-276) only demonstrates the branch by manually forging a dataset value that diverges from sendDisabled/disabled, a state that can't arise from this component's own render output.

If there's a concrete external mutator of data-send-disabled this is meant to guard against (e.g. non-React DOM manipulation elsewhere), please call it out in a comment; otherwise this is redundant surface area to maintain and reason about across two call sites.

♻️ Possible simplification
-    const domSendDisabled =
-      textareaRef.current?.dataset?.sendDisabled === "true";
     if (
       !sendContent ||
       disabled ||
       sendDisabled ||
       isSending ||
-      domSendDisabled ||
       sendBlockedRef.current
     ) {
       return;
     }
       if (e.key === "Enter" && !e.shiftKey) {
         e.preventDefault();
-        const domSendDisabled =
-          e.currentTarget?.dataset?.sendDisabled === "true" ||
-          textareaRef.current?.dataset?.sendDisabled === "true";
-        if (domSendDisabled || sendBlockedRef.current) return;
+        if (sendBlockedRef.current) return;
         handleSend();
       }

Also applies to: 341-353

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`
around lines 242 - 253, The new DOM dataset check in chat-input.js is redundant
with the existing send-blocking logic already covered by sendBlockedRef.current
and the explicit disabled/sendDisabled/isSending guards inside handleSend.
Remove the domSendDisabled read and its related branching unless there is a real
external mutation case to support, and keep the logic centered on the existing
ref-based state in ChatInput/handleSend so behavior is maintained in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_webui_v2/src/router.rs`:
- Around line 132-159: The
cached_global_auto_approve_feature/cache_global_auto_approve_feature logic is
memoizing a tenant/user-scoped setting that can change via Settings > Tools,
causing stale values to be replayed across requests. Remove this indefinite
cache for global_auto_approve or add invalidation tied to
AUTO_APPROVE_CONFIG_KEY updates so WebUiAuthenticatedCaller always reflects the
latest setting in GET /session.

---

Outside diff comments:
In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js`:
- Around line 242-253: The new DOM dataset check in chat-input.js is redundant
with the existing send-blocking logic already covered by sendBlockedRef.current
and the explicit disabled/sendDisabled/isSending guards inside handleSend.
Remove the domSendDisabled read and its related branching unless there is a real
external mutation case to support, and keep the logic centered on the existing
ref-based state in ChatInput/handleSend so behavior is maintained in one place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf34673f-bb05-4de3-846c-3670d06fe929

📥 Commits

Reviewing files that changed from the base of the PR and between 5524970 and 5d5e98d.

📒 Files selected for processing (7)
  • crates/ironclaw_product_workflow/src/reborn_services.rs
  • crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
  • crates/ironclaw_webui_v2/src/handlers.rs
  • crates/ironclaw_webui_v2/src/router.rs
  • crates/ironclaw_webui_v2/tests/webui_v2_handlers_contract.rs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/chat-input.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjs

Comment thread crates/ironclaw_webui_v2/src/router.rs Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 July 1, 2026 09:26 Destroyed
@italic-jinxin

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

This comment was marked as resolved.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5247 July 1, 2026 09:33 Destroyed
@think-in-universe
think-in-universe added this pull request to the merge queue Jul 1, 2026
Merged via the queue into main with commit 3469ea5 Jul 1, 2026
109 checks passed
@think-in-universe
think-in-universe deleted the issue-5246-global-auto-approve-link branch July 1, 2026 14:29
@coderabbitai coderabbitai Bot mentioned this pull request Jul 9, 2026
17 of 30 tasks

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5247 — b2824bb2 Deployed Jul 1, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules size: L 200-499 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Reborn] Add global auto-approve shortcut text under approval checkbox

2 participants