feat(frontend): Tools and Triggers extracted to @agenta/settings-ui, off antd - #5890
feat(frontend): Tools and Triggers extracted to @agenta/settings-ui, off antd#5890ardaerzin wants to merge 10 commits into
Conversation
|
@coderabbitai review |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR centralizes user-scoped state, adds shared voice-input components and hooks, introduces shared tools and trigger settings, and integrates these capabilities into mobile and OSS interfaces. ChangesShared user state and mobile settings
Shared voice input
Shared tools and triggers settings
Mobile error styling
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant VoiceInputButton
participant useVoiceInput
participant useAudioRecorder
participant MobileComposer
User->>VoiceInputButton: choose dictation or audio recording
VoiceInputButton->>useVoiceInput: start or stop dictation
VoiceInputButton->>useAudioRecorder: start or stop recording
useVoiceInput->>MobileComposer: update transcript text
useAudioRecorder->>MobileComposer: provide voice message file
MobileComposer->>MobileComposer: upload files and submit message
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
Rate Limit Exceeded
|
4fd20ad to
675fbd4
Compare
c1ee3bc to
7048eae
Compare
675fbd4 to
b303148
Compare
7048eae to
f8a5737
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (5)
web/mobile/src/features/settings/PreferencesTab.tsx (1)
18-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMemoize the
flagsprop.Lines 18-35 allocate a new object array on every render. Create
flagswithuseMemoand pass that value toPreferencesPage.As per coding guidelines, “Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders.”
Source: Coding guidelines
web/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsx (1)
203-246: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueWrap the
actionsrender prop inuseCallback.
actionsis a new function on every render, and it builds a fresh array of objects containing JSX for each row. Any row-level memoization insideDataTablecannot hold. The columns and rows above already useuseMemo, so this prop is the remaining unstable input.♻️ Proposed memoization
+ const actions = useCallback( + (record: SubscriptionRow) => [ + /* move the existing entries here unchanged */ + ], + [readOnly, openDeliveries, handleEdit, handleRefresh, handleRevoke, handleDelete], + )Then pass
actions={actions}toDataTable.As per coding guidelines: "Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders."
Source: Coding guidelines
web/packages/agenta-settings-ui/src/index.ts (1)
33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExport the props types for both trigger sections.
Both modules export their props types, but the barrel exports only the default components. Re-export
TriggerSchedulesSectionPropsandTriggerSubscriptionsSectionPropsso consumers can type wrappers around these sections.web/packages/agenta-settings-ui/package.json (1)
24-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMove
jotaiandjotai-tanstack-queryto peer dependencies.The current lockfile deduplicates Jotai to
2.20.0, but the dependency declarations can allow separate instances for consumers. Declare both packages as peers and keep them indevDependencies.jotai-tanstack-queryalso requires the host's@tanstack/react-queryand Jotai instances.web/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsx (1)
31-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExport one shared type for the
confirmcallback.This file declares
onOk: () => Promise<void>.GatewayToolsSection.tsxline 41 andConnectionsList.tsxline 16 declare the same callback asonOk: () => void | Promise<void>. Three inline copies with two signatures will drift, and the narrower signature here rejects a synchronousonOkfrom a caller that shares one adapter across sections. Declare the type once in the package and import it in all three files.♻️ Proposed refactor
// web/packages/agenta-settings-ui/src/types.ts export interface ConfirmArgs { title: string message: string onOk: () => void | Promise<void> } export type ConfirmFn = (args: ConfirmArgs) => void+import type {ConfirmFn} from "../types" + export interface TriggerConnectionsSectionProps { /** Destructive confirmation — the desktop's AlertPopup, a sheet elsewhere. */ - confirm?: (args: {title: string; message: string; onOk: () => Promise<void>}) => void + confirm?: ConfirmFn /** Hides connect/refresh and skips the catalog drawer. */ readOnly?: boolean }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 444dd719-48da-441d-b629-64c4df45d309
⛔ Files ignored due to path filters (2)
web/mobile/src/styles/theme.generated.cssis excluded by!**/*.generated.*web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (62)
web/mobile/scripts/generate-shadcn-tokens.tsweb/mobile/src/features/app/ContextSync.tsxweb/mobile/src/features/auth/useLogout.tsweb/mobile/src/features/chat/Composer.tsxweb/mobile/src/features/nav/DrawerProjectSwitcher.tsxweb/mobile/src/features/settings/AccountTab.tsxweb/mobile/src/features/settings/PreferencesTab.tsxweb/mobile/src/features/settings/SettingsScreen.tsxweb/mobile/src/styles/globals.cssweb/oss/src/components/AgentChatSlice/AgentConversation.tsxweb/oss/src/components/AgentChatSlice/assets/constants.tsweb/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsxweb/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsxweb/oss/src/components/AgentChatSlice/components/Inspector/InspectSessionButton.tsxweb/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.tsweb/oss/src/components/AgentChatSlice/hooks/useTurnInspector.tsweb/oss/src/components/SessionInspector/PanelSessionInspectorButton.tsxweb/oss/src/components/pages/settings/Account/DeleteAccount.tsxweb/oss/src/components/pages/settings/Preferences/Preferences.tsxweb/oss/src/components/pages/settings/Tools/Tools.tsxweb/oss/src/components/pages/settings/Tools/components/ActionsList.tsxweb/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsxweb/oss/src/components/pages/settings/Tools/components/ConnectModal.tsxweb/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsxweb/oss/src/components/pages/settings/Triggers/Triggers.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsxweb/oss/src/lib/onboarding/atoms.tsweb/oss/src/services/profile/index.tsweb/oss/src/state/settings/featureFlags.tsweb/packages/agenta-chat/src/assets/index.tsweb/packages/agenta-chat/src/assets/voice.tsweb/packages/agenta-chat/src/components/MicPermissionNotice.tsxweb/packages/agenta-chat/src/components/RecordingBar.tsxweb/packages/agenta-chat/src/components/RecordingWaveform.tsxweb/packages/agenta-chat/src/components/RevealCollapse.tsxweb/packages/agenta-chat/src/components/VoiceInputButton.tsxweb/packages/agenta-chat/src/components/index.tsweb/packages/agenta-chat/src/hooks/index.tsweb/packages/agenta-chat/src/hooks/useAudioRecorder.tsweb/packages/agenta-chat/src/hooks/useVoiceComposer.tsweb/packages/agenta-chat/src/hooks/useVoiceInput.tsweb/packages/agenta-chat/tests/unit/assets/voice.test.tsweb/packages/agenta-entities/src/profile/index.tsweb/packages/agenta-settings-ui/package.jsonweb/packages/agenta-settings-ui/src/index.tsweb/packages/agenta-settings-ui/src/tools/ActionsList.tsxweb/packages/agenta-settings-ui/src/tools/AgentaToolsPlaceholder.tsxweb/packages/agenta-settings-ui/src/tools/ConnectModal.tsxweb/packages/agenta-settings-ui/src/tools/ConnectionsList.tsxweb/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsxweb/packages/agenta-settings-ui/src/tools/IntegrationDetail.tsxweb/packages/agenta-settings-ui/src/tools/IntegrationGrid.tsxweb/packages/agenta-settings-ui/src/tools/hooks/useIntegrationDetail.tsweb/packages/agenta-settings-ui/src/tools/hooks/useToolsConnections.tsweb/packages/agenta-settings-ui/src/tools/hooks/useToolsIntegrations.tsweb/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsxweb/packages/agenta-settings-ui/src/triggers/TriggerSchedulesSection.tsxweb/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsxweb/packages/agenta-shared/src/state/featureFlags.tsweb/packages/agenta-shared/src/state/index.ts
💤 Files with no reviewable changes (9)
- web/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsx
- web/oss/src/state/settings/featureFlags.ts
- web/oss/src/components/AgentChatSlice/assets/constants.ts
- web/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsx
- web/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsx
- web/oss/src/components/pages/settings/Tools/components/ConnectModal.tsx
- web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
- web/oss/src/components/pages/settings/Tools/components/ActionsList.tsx
- web/oss/src/services/profile/index.ts
| // Per-user preferences (the Experiments switches) are scoped by this id, and they are read | ||
| // far from Settings — the chat composer asks whether voice is on. Written only once the | ||
| // profile answers: clearing it on a signed-out render would hand the next person on this | ||
| // browser a blank slate instead of their own settings. | ||
| useEffect(() => { | ||
| if (user?.id) setActiveUserId(user.id) | ||
| }, [user?.id, setActiveUserId]) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Clear the active-user preference scope when authentication ends.
The previous user ID remains in storage after logout because ContextSync only writes non-null IDs and useLogout does not reset the atom. A later user can read or modify preferences in the previous user scope before profile synchronization completes.
web/mobile/src/features/app/ContextSync.tsx#L17-L23: afteruseProfileresolves, writeuser?.id ?? nullto clear a stale scope for unauthenticated sessions.web/mobile/src/features/auth/useLogout.ts#L17-L21: resetactiveUserIdAtombefore redirecting to/auth.
📍 Affects 2 files
web/mobile/src/features/app/ContextSync.tsx#L17-L23(this comment)web/mobile/src/features/auth/useLogout.ts#L17-L21
| const submit = async (text: string, extraFiles: File[] = []) => { | ||
| const staged = attachments.files | ||
| const uploadedExtras = extraFiles.length | ||
| ? await attachments.uploadExtraFiles(extraFiles) | ||
| : [] | ||
| // A failed upload adopts the take into the tray; hold the send so nothing is lost. | ||
| if (!uploadedExtras) return | ||
| const outbound = [...staged, ...uploadedExtras] | ||
| try { | ||
| // `stagedFilesToParts` THROWS on a file whose upload hasn't settled — reachable via | ||
| // Enter, which the send button's `sendDisabled` guard doesn't cover. | ||
| const parts = staged.length > 0 ? stagedFilesToParts(staged, sessionId) : undefined | ||
| const parts = outbound.length > 0 ? stagedFilesToParts(outbound, sessionId) : undefined | ||
| await onSend({text, parts}) | ||
| attachments.clearAttachments(staged.map((file) => file.uid)) | ||
| } catch { | ||
| // Nothing consumes this promise (RichChatInput's submit is fire-and-forget), so an | ||
| // uncaught rejection would leave the user with no message, no error, and no idea a | ||
| // send even failed. Keep the attachments staged, put the text back, and say so | ||
| // through the composer's own inline channel — the same rejections strip the desktop | ||
| // uses when a staged file can't ride the send. | ||
| inputRef.current?.setMarkdown(text) | ||
| // through the composer's own inline channel. | ||
| richInputRef.current?.setMarkdown(text) | ||
| attachments.setRejections([{name: "Message", reason: "wasn't sent — try again."}]) | ||
| attachments.setAttachmentsOpen(true) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Locate runWithInFlightSubmit and check whether it is exported for cross-package use.
rg -nP --type=ts --type=tsx '\brunWithInFlightSubmit\b' web | head -50
fd -t f -e ts -e tsx . web/packages/agenta-chat/src | xargs -r rg -n 'InFlightSubmit'Repository: Agenta-AI/agenta
Length of output: 190
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- symbol references ---'
rg -n 'runWithInFlightSubmit' web 2>/dev/null | head -100 || true
printf '%s\n' '--- candidate chat package files ---'
if [ -d web/packages/agenta-chat/src ]; then
fd -t f -e ts -e tsx . web/packages/agenta-chat/src | head -100
else
printf '%s\n' 'web/packages/agenta-chat/src not found'
fi
printf '%s\n' '--- target file ---'
sed -n '1,140p' web/mobile/src/features/chat/Composer.tsxRepository: Agenta-AI/agenta
Length of output: 10379
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- in-flight helper ---'
cat -n web/oss/src/components/AgentChatSlice/assets/inFlightSubmit.ts
printf '%s\n' '--- desktop usage ---'
sed -n '420,455p' web/oss/src/components/AgentChatSlice/AgentConversation.tsx
printf '%s\n' '--- ChatComposer submit wiring ---'
rg -n -C 8 'onSubmit|submit\\(' web/packages/agenta-chat/src/components/ChatComposer.tsx web/packages/agenta-chat/src/hooks/useVoiceComposer.ts web/packages/agenta-chat/src/hooks/useComposerAttachments.ts
printf '%s\n' '--- attachment state and uploads ---'
rg -n -C 8 'files|uploadExtraFiles|clearAttachments|addFiles' web/packages/agenta-chat/src/hooks/useComposerAttachments.ts web/packages/agenta-chat/src/hooks/useAttachmentUploads.ts web/packages/agenta-chat/src/model/attachments.tsRepository: Agenta-AI/agenta
Length of output: 38039
🏁 Script executed:
#!/bin/bash
set -u
python3 - <<'PY'
from pathlib import Path
paths = [
Path("web/oss/src/components/AgentChatSlice/assets/inFlightSubmit.ts"),
Path("web/packages/agenta-chat/src/components/ChatComposer.tsx"),
Path("web/packages/agenta-chat/src/hooks/useVoiceComposer.ts"),
Path("web/packages/agenta-chat/src/hooks/useComposerAttachments.ts"),
Path("web/packages/agenta-chat/src/hooks/useAttachmentUploads.ts"),
]
for path in paths:
print(f"--- {path} ---")
if not path.exists():
print("MISSING")
continue
lines = path.read_text().splitlines()
for i, line in enumerate(lines, 1):
if path.name == "inFlightSubmit.ts" or any(k in line for k in (
"onSubmit", "submit", "uploadExtraFiles", "clearAttachments",
"files", "addFiles", "onSendVoiceMessage", "voice",
)):
lo, hi = max(1, i - 3), min(len(lines), i + 8)
for n in range(lo, hi + 1):
print(f"{n:4}: {lines[n-1]}")
print()
PYRepository: Agenta-AI/agenta
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- exact uploadExtraFiles implementation ---'
sed -n '177,217p' web/packages/agenta-chat/src/hooks/useComposerAttachments.ts
printf '%s\n' '--- rich input implementation files ---'
fd -i -t f -e ts -e tsx . web | rg '/(RichChatInput|rich-chat-input|richChatInput)' | head -30
printf '%s\n' '--- exports for the helper and chat package ---'
cat -n web/oss/src/components/AgentChatSlice/assets/index.ts 2>/dev/null || true
cat -n web/packages/agenta-chat/src/hooks/index.ts
cat -n web/packages/agenta-chat/src/index.ts
printf '%s\n' '--- mobile package imports and package boundaries ---'
rg -n 'from "`@agenta/oss`|from "`@agenta/chat`|inFlightSubmit|AgentConversation' web/mobile web/packages/agenta-chat web/oss | head -100Repository: Agenta-AI/agenta
Length of output: 16444
🏁 Script executed:
#!/bin/bash
set -u
python3 - <<'PY'
import asyncio
async def simulate():
files = [{"uid": "staged-1"}]
clear_calls = []
sends = []
async def upload_extra_files():
await asyncio.sleep(0)
return []
async def on_send(payload):
sends.append(payload)
await asyncio.sleep(0)
async def submit(text):
staged = files[:] # each invocation captures the same current tray
await upload_extra_files()
outbound = staged
await on_send({"text": text, "parts": outbound})
clear_calls.append([f["uid"] for f in staged])
await asyncio.gather(submit("first"), submit("second"))
print({"send_count": len(sends), "sent_uids": [[f["uid"] for f in p["parts"]] for p in sends],
"clear_calls": clear_calls})
asyncio.run(simulate())
PYRepository: Agenta-AI/agenta
Length of output: 260
🏁 Script executed:
#!/bin/bash
set -u
python3 - <<'PY'
import asyncio
async def simulate():
files = [{"uid": "staged-1"}]
clear_calls = []
sends = []
async def upload_extra_files():
await asyncio.sleep(0)
return []
async def on_send(payload):
sends.append(payload)
await asyncio.sleep(0)
async def submit(text):
staged = files[:]
await upload_extra_files()
await on_send({"text": text, "parts": staged})
clear_calls.append([f["uid"] for f in staged])
await asyncio.gather(submit("first"), submit("second"))
print({
"send_count": len(sends),
"sent_uids": [[f["uid"] for f in p["parts"]] for p in sends],
"clear_calls": clear_calls,
})
asyncio.run(simulate())
PYRepository: Agenta-AI/agenta
Length of output: 260
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- submit plugin and rich input event path ---'
cat -n web/packages/agenta-ui/src/RichChatInput/plugins/SubmitPlugin.ts
sed -n '1,240p' web/packages/agenta-ui/src/RichChatInput/RichChatInput.tsx
printf '%s\n' '--- send button gating ---'
cat -n web/packages/agenta-ui/src/RichChatInput/ComposerSendButton.tsx
cat -n web/packages/agenta-ui/src/RichChatInput/plugins/SendButton.tsx
printf '%s\n' '--- helper package metadata and aliases ---'
rg -n -C 3 '"`@agenta/`(chat|oss)"|agenta-chat|paths' web/tsconfig*.json web/*/tsconfig*.json web/package.json web/pnpm-workspace.yaml 2>/dev/null | head -120Repository: Agenta-AI/agenta
Length of output: 21139
Guard submit against concurrent sends.
Enter and voice submissions can overlap while uploadExtraFiles or onSend awaits. Both calls can send the same staged files and clear them twice. Move runWithInFlightSubmit from the OSS-only path into @agenta/chat, export it, and wrap this submit with it.
| // gap-8: sections are separated by whitespace, never a rule. | ||
| return ( | ||
| <div className="flex flex-col"> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The comment and the class list disagree about the section gap.
Line 7 documents gap-8. Line 9 sets className="flex flex-col" with no gap utility, so the three trigger sections render flush against each other. The sibling page web/oss/src/components/pages/settings/Tools/Tools.tsx uses flex flex-col gap-8. Restore the gap, or remove the stale comment if the sections now own their own spacing.
🐛 Proposed fix
// gap-8: sections are separated by whitespace, never a rule.
return (
- <div className="flex flex-col">
+ <div className="flex flex-col gap-8">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // gap-8: sections are separated by whitespace, never a rule. | |
| return ( | |
| <div className="flex flex-col"> | |
| // gap-8: sections are separated by whitespace, never a rule. | |
| return ( | |
| <div className="flex flex-col gap-8"> |
| export const deleteAccount = async (): Promise<void> => { | ||
| await axios.delete(`${getAgentaApiUrl()}/profile`) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline web/packages/agenta-sdk/src/resources.ts --items all
rg -n -C 3 'profile|axios\.(get|delete)' \
web/packages/agenta-sdk/src/resources.ts \
web/packages/agenta-entities/src/profile/index.tsRepository: Agenta-AI/agenta
Length of output: 5476
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- profile entity module ---'
cat -n web/packages/agenta-entities/src/profile/index.ts
printf '%s\n' '--- SDK resource accessors ---'
cat -n web/packages/agenta-sdk/src/resources.ts
printf '%s\n' '--- profile client and endpoint definitions ---'
rg -n -C 4 'Profile|profile|deleteAccount|delete.*profile|/profile' \
web/packages/agenta-sdk web/packages/agenta-entities web/packages \
-g '*.ts' -g '*.tsx' -g '*.json' | head -n 300Repository: Agenta-AI/agenta
Length of output: 37935
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- tracked API-client files ---'
git ls-files | rg '(^|/)(api-client|generated|resources)(/|\\.)|agentaai/api-client|openapi|fern' | head -n 300
printf '%s\n' '--- package references and workspace metadata ---'
rg -n -C 3 '`@agentaai/api-client`|`@agenta/sdk/resources`|agenta-sdk|profile' \
web/package.json web/pnpm-lock.yaml web/packages/*/package.json \
pnpm-workspace.yaml package.json 2>/dev/null | head -n 300
printf '%s\n' '--- all tracked profile/API endpoint references ---'
git grep -n -E '(/profile|deleteAccount|ProfileClient|profileClient)' -- \
':!web/packages/agenta-entities/src/profile/index.ts' \
':!web/packages/agenta-sdk/src/resources.ts' | head -n 300Repository: Agenta-AI/agenta
Length of output: 30185
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- generated resource directories ---'
git ls-files 'web/packages/agenta-api-client/src/generated/api/resources/*' \
| sed -n '1,500p' \
| awk -F'/resources/' 'NF > 1 {split($2, a, "/"); print a[1]}' \
| sort -u
printf '%s\n' '--- profile matches in generated client and SDK metadata ---'
rg -n -i -C 4 'profile|delete.*account|account.*delete|/profile' \
web/packages/agenta-api-client \
web/packages/agenta-sdk \
web/package.json \
web/packages/agenta-api-client/package.json \
web/packages/agenta-sdk/package.json \
2>/dev/null || true
printf '%s\n' '--- relevant package exports and generation scripts ---'
cat -n web/packages/agenta-api-client/package.json
cat -n web/packages/agenta-sdk/package.jsonRepository: Agenta-AI/agenta
Length of output: 25990
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- users resource exports ---'
cat -n web/packages/agenta-api-client/src/generated/api/resources/users/index.ts
cat -n web/packages/agenta-api-client/src/generated/api/resources/users/exports.ts
cat -n web/packages/agenta-api-client/src/generated/api/resources/index.ts
printf '%s\n' '--- SDK config and accessor conventions ---'
cat -n web/packages/agenta-sdk/src/config.ts
sed -n '1,140p' web/packages/agenta-sdk/src/resources.ts
printf '%s\n' '--- users client implementation ---'
sed -n '1,145p' web/packages/agenta-api-client/src/generated/api/resources/users/client/Client.tsRepository: Agenta-AI/agenta
Length of output: 19608
Use the Fern users resource accessor.
Expose getUsersClient() from web/packages/agenta-sdk/src/resources.ts and call getUsersClient().deleteUserAccount() instead of raw Axios.
Source: Coding guidelines
| const result = await handleRefresh(connection.id) | ||
| const redirectUrl = getRedirectUrl(result.connection) | ||
|
|
||
| if (!redirectUrl) return |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Handle a rejected refresh request.
handleRefresh calls the gateway API without a try/catch here. The caller at line 114 uses void onRefresh(record), so a rejection becomes an unhandled promise rejection and the user receives no feedback. Wrap the call and report the failure, as GatewayToolsSection does.
🛠️ Proposed fix
- const result = await handleRefresh(connection.id)
- const redirectUrl = getRedirectUrl(result.connection)
+ let redirectUrl: string | undefined
+ try {
+ const result = await handleRefresh(connection.id)
+ redirectUrl = getRedirectUrl(result.connection)
+ } catch {
+ message.error("Failed to refresh connection")
+ return
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const result = await handleRefresh(connection.id) | |
| const redirectUrl = getRedirectUrl(result.connection) | |
| if (!redirectUrl) return | |
| let redirectUrl: string | undefined | |
| try { | |
| const result = await handleRefresh(connection.id) | |
| redirectUrl = getRedirectUrl(result.connection) | |
| } catch { | |
| message.error("Failed to refresh connection") | |
| return | |
| } | |
| if (!redirectUrl) return |
| const confirmDelete = useCallback( | ||
| (connection: ToolConnection) => { | ||
| AlertPopup({ | ||
| confirm?.({ |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The new confirm prop is captured but missing from both dependency arrays. The migration replaced the directly imported AlertPopup with the injected confirm prop, so both memoized handlers now close over a prop that is not declared as a dependency. A new confirm identity from the parent leaves the stale reference in place and the confirmation dialog stops opening. TriggerConnectionsSection.tsx lists confirm correctly at lines 107 and 127.
web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L153-L155: change theconfirmDeletedependency array at line 170 to[confirm, handleDelete].web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L173-L175: change theconfirmRevokedependency array at line 190 to[confirm, handleRevoke].
📍 Affects 1 file
web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L153-L155(this comment)web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L173-L175
| <Input | ||
| placeholder="Search integrations…" | ||
| value={search} | ||
| onChange={(e) => setSearch(e.target.value)} | ||
| className="pl-9" | ||
| /> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Give the search input an accessible name.
The input carries a placeholder only. The adjacent MagnifyingGlass icon is decorative and pointer-events-none, so no label is associated with the field. Screen reader users receive no stable name. Add aria-label.
♿ Proposed fix
<Input
+ aria-label="Search integrations"
placeholder="Search integrations…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Input | |
| placeholder="Search integrations…" | |
| value={search} | |
| onChange={(e) => setSearch(e.target.value)} | |
| className="pl-9" | |
| /> | |
| <Input | |
| aria-label="Search integrations" | |
| placeholder="Search integrations…" | |
| value={search} | |
| onChange={(e) => setSearch(e.target.value)} | |
| className="pl-9" | |
| /> |
| <button | ||
| type="button" | ||
| onClick={onClick} | ||
| className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary" | ||
| > |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a visible focus state to the integration card button.
The card is now a native button, so keyboard users can reach it. The class list defines a hover border only. Without a focus style, keyboard users cannot see the current card. Add a focus-visible ring that works in light and dark themes.
♿ Proposed fix
- className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary"
+ className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary focus-visible:border-colorPrimary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-colorPrimary"As per coding guidelines: "Implement light and dark appearance and interaction states for every added or changed UI element, and verify both themes."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| type="button" | |
| onClick={onClick} | |
| className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary" | |
| > | |
| <button | |
| type="button" | |
| onClick={onClick} | |
| className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary focus-visible:border-colorPrimary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-colorPrimary" | |
| > |
Source: Coding guidelines
| { | ||
| key: "delete", | ||
| label: "Delete", | ||
| icon: <Trash size={16} />, | ||
| danger: true, | ||
| hidden: readOnly, | ||
| onClick: () => handleDelete(record), | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Require confirmation before a schedule is deleted.
The delete action calls handleDelete(record) directly, which removes the schedule immediately. The operation is irreversible. GatewayToolsSection and TriggerConnectionsSection both route delete through an injected confirm callback and hide the action when confirm is absent. Add the same confirm prop here and apply it to delete.
🛡️ Proposed fix
export interface TriggerSchedulesSectionProps {
+ /** Destructive confirmation — the desktop's AlertPopup, a sheet elsewhere. */
+ confirm?: (args: {title: string; message: string; onOk: () => void | Promise<void>}) => void
/** Hides create/edit and skips the drawer, which is still antd-backed. */
readOnly?: boolean
} {
key: "delete",
label: "Delete",
icon: <Trash size={16} />,
danger: true,
- hidden: readOnly,
- onClick: () => handleDelete(record),
+ hidden: readOnly || !confirm,
+ onClick: () =>
+ confirm?.({
+ title: "Delete Schedule",
+ message:
+ "Are you sure you want to delete this schedule? This action is irreversible.",
+ onOk: () => handleDelete(record),
+ }),
},| const handleDelete = useCallback( | ||
| async (record: TriggerSubscription) => { | ||
| if (!record.id) return | ||
| try { | ||
| await remove(record.id) | ||
| message.success("Subscription deleted") | ||
| } catch { | ||
| message.error("Failed to delete subscription") | ||
| } | ||
| }, | ||
| [remove], | ||
| ) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Add a confirmation step before delete and revoke.
handleDelete calls remove(record.id) immediately, and handleRevoke calls revoke(record.id) immediately. The row actions at Lines 230-245 invoke them on a single click. A user can permanently delete a subscription with no confirmation and no undo.
The sibling sections take a host-supplied confirm dialog for exactly this reason. TriggerConnectionsSection and GatewayToolsSection both accept a confirm prop, and OSS passes AlertPopup in web/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsx and web/oss/src/components/pages/settings/Tools/Tools.tsx. This section accepts only readOnly, so the destructive-action contract is inconsistent across the migrated trigger sections.
Add an optional confirm prop with the same shape the other sections use. If a host passes no confirm, hide the destructive actions, which matches the behavior described for GatewayToolsSection.
🛡️ Proposed confirm prop wiring
export interface TriggerSubscriptionsSectionProps {
/** Hides create/edit and skips the drawer, whose form is still antd-backed. */
readOnly?: boolean
+ /** Host confirm dialog. Destructive actions stay hidden when a host brings none. */
+ confirm?: (options: {
+ title: string
+ message: string
+ okText?: string
+ cancelText?: string
+ type?: "warning" | "error" | "info"
+ onOk: () => void | Promise<void>
+ }) => void
}
export default function TriggerSubscriptionsSection({
readOnly,
+ confirm,
}: TriggerSubscriptionsSectionProps = {}) { const handleDelete = useCallback(
- async (record: TriggerSubscription) => {
- if (!record.id) return
- try {
- await remove(record.id)
- message.success("Subscription deleted")
- } catch {
- message.error("Failed to delete subscription")
- }
- },
- [remove],
+ (record: TriggerSubscription) => {
+ if (!record.id || !confirm) return
+ const id = record.id
+ confirm({
+ title: "Delete subscription",
+ message: "This deletes the subscription permanently.",
+ okText: "Delete",
+ type: "warning",
+ onOk: async () => {
+ try {
+ await remove(id)
+ message.success("Subscription deleted")
+ } catch {
+ message.error("Failed to delete subscription")
+ }
+ },
+ })
+ },
+ [remove, confirm],
)Apply the same pattern to handleRevoke, then hide both actions when no confirm is supplied:
{
key: "revoke",
- hidden: readOnly,
+ hidden: readOnly || !confirm,
label: "Revoke", {
key: "delete",
label: "Delete",
icon: <Trash size={16} />,
danger: true,
- hidden: readOnly,
+ hidden: readOnly || !confirm,
onClick: () => handleDelete(record),
},Run the following script to confirm that the replaced OSS implementation used a confirmation dialog:
#!/bin/bash
# Description: Check the confirm-prop contract across the migrated settings sections and their OSS bindings.
set -euo pipefail
echo "== confirm prop across settings-ui sections =="
rg -n -C 4 'confirm' web/packages/agenta-settings-ui/src/triggers web/packages/agenta-settings-ui/src/tools
echo "== OSS bindings passing AlertPopup =="
rg -n -C 3 'AlertPopup' web/oss/src/components/pages/settings
echo "== history of the replaced subscriptions section =="
git log --oneline -n 20 -- 'web/oss/src/components/pages/settings/Triggers/**'There was a problem hiding this comment.
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: 13
🧹 Nitpick comments (5)
web/mobile/src/features/settings/PreferencesTab.tsx (1)
18-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMemoize the
flagsprop.Lines 18-35 allocate a new object array on every render. Create
flagswithuseMemoand pass that value toPreferencesPage.As per coding guidelines, “Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders.”
Source: Coding guidelines
web/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsx (1)
203-246: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueWrap the
actionsrender prop inuseCallback.
actionsis a new function on every render, and it builds a fresh array of objects containing JSX for each row. Any row-level memoization insideDataTablecannot hold. The columns and rows above already useuseMemo, so this prop is the remaining unstable input.♻️ Proposed memoization
+ const actions = useCallback( + (record: SubscriptionRow) => [ + /* move the existing entries here unchanged */ + ], + [readOnly, openDeliveries, handleEdit, handleRefresh, handleRevoke, handleDelete], + )Then pass
actions={actions}toDataTable.As per coding guidelines: "Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders."
Source: Coding guidelines
web/packages/agenta-settings-ui/src/index.ts (1)
33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExport the props types for both trigger sections.
Both modules export their props types, but the barrel exports only the default components. Re-export
TriggerSchedulesSectionPropsandTriggerSubscriptionsSectionPropsso consumers can type wrappers around these sections.web/packages/agenta-settings-ui/package.json (1)
24-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMove
jotaiandjotai-tanstack-queryto peer dependencies.The current lockfile deduplicates Jotai to
2.20.0, but the dependency declarations can allow separate instances for consumers. Declare both packages as peers and keep them indevDependencies.jotai-tanstack-queryalso requires the host's@tanstack/react-queryand Jotai instances.web/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsx (1)
31-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExport one shared type for the
confirmcallback.This file declares
onOk: () => Promise<void>.GatewayToolsSection.tsxline 41 andConnectionsList.tsxline 16 declare the same callback asonOk: () => void | Promise<void>. Three inline copies with two signatures will drift, and the narrower signature here rejects a synchronousonOkfrom a caller that shares one adapter across sections. Declare the type once in the package and import it in all three files.♻️ Proposed refactor
// web/packages/agenta-settings-ui/src/types.ts export interface ConfirmArgs { title: string message: string onOk: () => void | Promise<void> } export type ConfirmFn = (args: ConfirmArgs) => void+import type {ConfirmFn} from "../types" + export interface TriggerConnectionsSectionProps { /** Destructive confirmation — the desktop's AlertPopup, a sheet elsewhere. */ - confirm?: (args: {title: string; message: string; onOk: () => Promise<void>}) => void + confirm?: ConfirmFn /** Hides connect/refresh and skips the catalog drawer. */ readOnly?: boolean }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 444dd719-48da-441d-b629-64c4df45d309
⛔ Files ignored due to path filters (2)
web/mobile/src/styles/theme.generated.cssis excluded by!**/*.generated.*web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (62)
web/mobile/scripts/generate-shadcn-tokens.tsweb/mobile/src/features/app/ContextSync.tsxweb/mobile/src/features/auth/useLogout.tsweb/mobile/src/features/chat/Composer.tsxweb/mobile/src/features/nav/DrawerProjectSwitcher.tsxweb/mobile/src/features/settings/AccountTab.tsxweb/mobile/src/features/settings/PreferencesTab.tsxweb/mobile/src/features/settings/SettingsScreen.tsxweb/mobile/src/styles/globals.cssweb/oss/src/components/AgentChatSlice/AgentConversation.tsxweb/oss/src/components/AgentChatSlice/assets/constants.tsweb/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsxweb/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsxweb/oss/src/components/AgentChatSlice/components/Inspector/InspectSessionButton.tsxweb/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.tsweb/oss/src/components/AgentChatSlice/hooks/useTurnInspector.tsweb/oss/src/components/SessionInspector/PanelSessionInspectorButton.tsxweb/oss/src/components/pages/settings/Account/DeleteAccount.tsxweb/oss/src/components/pages/settings/Preferences/Preferences.tsxweb/oss/src/components/pages/settings/Tools/Tools.tsxweb/oss/src/components/pages/settings/Tools/components/ActionsList.tsxweb/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsxweb/oss/src/components/pages/settings/Tools/components/ConnectModal.tsxweb/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsxweb/oss/src/components/pages/settings/Triggers/Triggers.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsxweb/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsxweb/oss/src/lib/onboarding/atoms.tsweb/oss/src/services/profile/index.tsweb/oss/src/state/settings/featureFlags.tsweb/packages/agenta-chat/src/assets/index.tsweb/packages/agenta-chat/src/assets/voice.tsweb/packages/agenta-chat/src/components/MicPermissionNotice.tsxweb/packages/agenta-chat/src/components/RecordingBar.tsxweb/packages/agenta-chat/src/components/RecordingWaveform.tsxweb/packages/agenta-chat/src/components/RevealCollapse.tsxweb/packages/agenta-chat/src/components/VoiceInputButton.tsxweb/packages/agenta-chat/src/components/index.tsweb/packages/agenta-chat/src/hooks/index.tsweb/packages/agenta-chat/src/hooks/useAudioRecorder.tsweb/packages/agenta-chat/src/hooks/useVoiceComposer.tsweb/packages/agenta-chat/src/hooks/useVoiceInput.tsweb/packages/agenta-chat/tests/unit/assets/voice.test.tsweb/packages/agenta-entities/src/profile/index.tsweb/packages/agenta-settings-ui/package.jsonweb/packages/agenta-settings-ui/src/index.tsweb/packages/agenta-settings-ui/src/tools/ActionsList.tsxweb/packages/agenta-settings-ui/src/tools/AgentaToolsPlaceholder.tsxweb/packages/agenta-settings-ui/src/tools/ConnectModal.tsxweb/packages/agenta-settings-ui/src/tools/ConnectionsList.tsxweb/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsxweb/packages/agenta-settings-ui/src/tools/IntegrationDetail.tsxweb/packages/agenta-settings-ui/src/tools/IntegrationGrid.tsxweb/packages/agenta-settings-ui/src/tools/hooks/useIntegrationDetail.tsweb/packages/agenta-settings-ui/src/tools/hooks/useToolsConnections.tsweb/packages/agenta-settings-ui/src/tools/hooks/useToolsIntegrations.tsweb/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsxweb/packages/agenta-settings-ui/src/triggers/TriggerSchedulesSection.tsxweb/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsxweb/packages/agenta-shared/src/state/featureFlags.tsweb/packages/agenta-shared/src/state/index.ts
💤 Files with no reviewable changes (9)
- web/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsx
- web/oss/src/state/settings/featureFlags.ts
- web/oss/src/components/AgentChatSlice/assets/constants.ts
- web/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsx
- web/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsx
- web/oss/src/components/pages/settings/Tools/components/ConnectModal.tsx
- web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
- web/oss/src/components/pages/settings/Tools/components/ActionsList.tsx
- web/oss/src/services/profile/index.ts
🛑 Comments failed to post (1)
web/packages/agenta-chat/src/hooks/useAudioRecorder.ts (1)
138-143: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Guard
startagainst re-entry while the permission request is pending.The guard only checks
recRef.current. Duringstatus === "requesting"that ref is still null, so a secondstart()call issues a secondgetUserMedia. The first resolved stream then overwritesstreamRef.current, and its tracks are never stopped. The microphone stays open until the page unloads, and twoMediaRecorderinstances can exist.🔒 Proposed fix using a pending ref
const cancelledRef = useRef(false) + // `recRef` is still null while the permission prompt is up, so it cannot block a second start. + const requestingRef = useRef(false)const start = useCallback(() => { - if (!supported || recRef.current) return + if (!supported || recRef.current || requestingRef.current) return setError(null) cancelledRef.current = false erroredRef.current = false + requestingRef.current = true setStatus("requesting")Clear the flag in both settlement paths:
.then((stream) => { + requestingRef.current = false if (cancelledRef.current) {.catch((e: unknown) => { + requestingRef.current = false teardown()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const start = useCallback(() => { if (!supported || recRef.current || requestingRef.current) return setError(null) cancelledRef.current = false erroredRef.current = false requestingRef.current = true setStatus("requesting")
Tools and Triggers turned out to be nearly free of app-layer coupling — between them they reach @/oss for AlertPopup and one URL helper, nothing else. Their data already comes from @agenta/entities and @agenta/entity-ui, so antd was the only thing keeping them out of the package. The connections list moves first. AlertPopup becomes a `confirm` prop, and the two actions that need it hide themselves when a host does not pass one, rather than opening nothing. OSS keeps a four-line binding at the old path so the Triggers page is unchanged. Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
Second of the three Triggers sections. Same conversion as the connections list: DataTable, @agenta/ui primitives, no antd. The cron column's hover tooltip becomes a title attribute — a Radix tooltip per row is a popper per row, and the raw cron string is a hint, not a control. Triggers.tsx imports this one from the package directly rather than through a re-export stub at the old path, which OSS lint forbids. Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
Last of the three Triggers sections. Triggers.tsx now composes two shared sections and one thin OSS binding, which exists only because the connections list needs AlertPopup. The Subscribe button's tooltip moves onto a wrapping span: a disabled button fires no pointer events, so antd's Tooltip-on-disabled-child behaviour has to be reproduced deliberately or the "Connect an app first" hint never appears — which is the one case where it matters. Also drops `fixed: "left"` from the columns carried over in all three sections. It was an antd frozen-column setting; DataTable does not freeze, so it was inert. Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
The whole Tools tree moves: the section, the integration grid and detail pane, the connections and actions lists, the connect dialog, and the three hooks. OSS keeps a Tools.tsx that passes AlertPopup as `confirm`. Three conversions worth naming: ConnectModal dropped antd's Form for controlled state and Field. antd's validateFields both validated and read the values; with one required field that is a trim check, so the form instance bought nothing a package can use. The integration cards were antd Card with onClick — now real buttons, so they are keyboard-reachable and announce as controls, which a clickable div never did. Catalog logos drop next/image for img. The URLs are remote and arbitrary, and next/image needs every host allow-listed in next.config — configuration a package cannot own and should not require of its consumers. useToolsConnections had one consumer outside settings (the chat client-tools connect flow), which now imports it from the package. Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile. No antd left in either the tools or triggers directories.
Both were extracted last commit but wiring them as-is would have shipped the bug from the white settings tables again. The lists are antd-free now, but the create/edit drawers are not: TriggerSubscriptionDrawer and the tool execution drawer render SubscriptionForm and SchemaForm, 2306 lines still built on antd Form. With no ConfigProvider on /m those come out light on a dark page. So the four sections take `readOnly`, which hides create/edit/run and does not mount the drawers at all — nothing to open, rather than something that opens broken. /m passes it; OSS does not and is unchanged. canShowTools/canShowTriggers flip to true in the mobile access object, which was the only thing keeping both tabs off the rail. Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
… and a failure Three defects the extraction introduced or carried over: - `confirmDelete`/`confirmRevoke` closed over the injected `confirm` prop without listing it, so a new `confirm` identity from the host left the stale closure in place and the delete/revoke dialog silently stopped opening. The sibling `TriggerConnectionsSection` already lists it; match it. - The OAuth popup poll lived inside the async handler with no cleanup path, so an unmount mid-flow left an interval running for the life of the page, then called `invalidate()`, `handleClose()` and setState against a dead component. Hold the timer in a ref and clear it on unmount as well as on popup-close. - A rejected create (ConnectModal) and a rejected refresh (ConnectionsList) told the user nothing — the modal just sat there. Both now report through `@agenta/ui/app-message`, the channel the section already uses.
The search input had no accessible name (a placeholder is not one), and the integration cards — buttons — had a hover border but no focus state, so a keyboard user could not see where they were in the grid. The cards take the package's shared focus ring (the one Button, Segmented and Checkbox use): a 4px token-driven outline on :focus-visible only, correct in both themes and absent on a mouse click.
The comment says "gap-8: sections are separated by whitespace, never a rule", but the extraction dropped the class along with the old gap-6 header block, so the three sections butted straight into each other. Every sibling settings page that composes sections this way — Tools, Secrets, Vault — uses gap-8, so the comment is the correct half and the class is the one to fix.
Nothing guarded `submit` against re-entry, and Enter is not covered by the send button's disabled state — a second Enter during the upload/send round-trip re-read the same staged tray and posted the message twice. A ref guard released in `finally` covers both outcomes, so the recently added failure path (which restores the draft and reports through the attachments tray) still unlocks the composer instead of wedging it.
`activeUserIdAtom` was written when a profile resolved and never cleared, so the Experiments switches and the composer's voice setting could carry into the next person to use the browser. The file's existing intent is kept: a *falsy* profile is not a finished one — `user` is null while the query is in flight (the id is storage-backed precisely so preferences resolve before that lands) and null when the request failed, and clearing on either would hand a returning user a blank slate mid-load. Only a settled answer — not pending, no error, still null — is the 401 that means the session is over, and that clears the scope. Sign-out is the unambiguous signal, so it clears there too, and drops the cached profile with it: that entry is the ended session's identity, and leaving it would let the stale answer write the old id straight back. Clearing removes only the pointer — each user's flags stay under their own `agenta:settings:<id>:*` keys.
b303148 to
35f4b25
Compare
f8a5737 to
d557bed
Compare
Trigger connections, scheduled runs and event subscriptions move into
@agenta/settings-ui, andall seven Tools files follow. Both sections are off antd as a result.
/mgains Tools and Triggers here, read-only — they become writable two lanes up, once the formengine migration lands.
Not run in a browser — static gates only (
pnpm lint-fix24/24,tsc --noEmitcleanfor
@agenta/shared,ui,entities,entity-ui,settings-ui,oss,ee,mobile).Stacked on
pkg/settings-org-pages; review only this lane's diff.