fix(frontend): DataTable at phone widths, /m Tools and Triggers writable, and the shared drill-in bridge - #5893
fix(frontend): DataTable at phone widths, /m Tools and Triggers writable, and the shared drill-in bridge#5893ardaerzin wants to merge 16 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 workflow-reference and page-title utilities, adds mobile settings tabs and responsive sheets, migrates ChangesShared workflow and title infrastructure
Mobile settings and responsive UI
State and UI integration
Estimated code review effort: 5 (Critical) | ~120 minutes 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
|
2d2afc2 to
f6bfe32
Compare
e93a361 to
f8b9cc7
Compare
f6bfe32 to
9ceda16
Compare
f8b9cc7 to
4fd0ddd
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (6)
web/packages/agenta-entity-ui/src/DrillInView/bridges/useWorkflowReferenceBridge.ts (2)
537-590: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDeduplicate the three
retrieveWorkflowRevisioncalls per workflow.
resolveInputSchema,resolveOutputSchema, andresolveConfigPayloadeach fetch the same revision by slug. When the reference drawer opens, this produces three identical requests, and the three results can disagree if a commit lands between them.Cache the revision per
projectId+slug, for example through a shared promise map or a TanStack QueryfetchQuery, and have all three resolvers read it.
46-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winImport
atomFamilyfromjotai-family.This file still imports it from
jotai/utils, while the package usesjotai-familyelsewhere. The package already declaresjotai-familyas a peer dependency.web/mobile/src/components/PageTitle.tsx (1)
4-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShorten the in-code documentation.
This four-line comment duplicates the formatter contract. Replace it with one short line, or move the full behavior description to external documentation.
As per coding guidelines, keep in-code comments to at most one short line unless they document a genuinely surprising constraint.
Source: Coding guidelines
web/mobile/src/features/settings/LlmProvidersTab.tsx (1)
17-19: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueBoth provider tables share one
deletinganderrorstate.
deleteDialogreads the sameerrorfor the standard table and the custom table. If a delete fails in one table, and the user then opens the delete confirm in the other table, the stale message appears. The table controlsopendirectly, soonClosedoes not run first.Key the state by provider id, or reset
errorwhenselectedProviderchanges.Also applies to: 21-62
web/mobile/src/features/settings/SecretFormSheet.tsx (1)
191-202: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract the
textareastyling into a shared component.The class list on Line 201 restates the
Inputcomponent's border, focus-ring, and dark-mode rules. A copy drifts fromInputthe next time the input styling changes. Add aTextareacomponent next to@/components/ui/inputand use it here.web/mobile/src/features/settings/useConfirmSheet.tsx (1)
5-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLet the caller set the confirm label and the destructive style.
ConfirmSheetdefaultsdestructivetotrue, and this hook always passesconfirmLabel="Confirm". Every confirmation from the shared sections therefore shows a red generic button, including a non-destructive one. Add optionalconfirmLabelanddestructivefields toConfirmRequestand forward them.Also applies to: 32-54
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 16641e06-c39c-4abc-b4b5-39b81fd6cd91
⛔ Files ignored due to path filters (1)
web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (87)
web/mobile/next.config.tsweb/mobile/package.jsonweb/mobile/src/components/PageTitle.tsxweb/mobile/src/components/ui/sheet.tsxweb/mobile/src/features/agents/AgentListScreen.tsxweb/mobile/src/features/agents/AgentOverviewScreen.tsxweb/mobile/src/features/agents/AgentTemplateDetailScreen.tsxweb/mobile/src/features/agents/AgentTemplatesScreen.tsxweb/mobile/src/features/chat/ConfigPane.tsxweb/mobile/src/features/chat/DrillInBridgeProvider.tsxweb/mobile/src/features/chat/SessionTabs.tsxweb/mobile/src/features/chat/selectedRevision.tsweb/mobile/src/features/context/ContextResolver.tsxweb/mobile/src/features/home/HomeScreen.tsxweb/mobile/src/features/nav/useMobileNavItems.tsxweb/mobile/src/features/sessions/SessionListScreen.tsxweb/mobile/src/features/settings/AccountTab.tsxweb/mobile/src/features/settings/CancelSubscriptionSheet.tsxweb/mobile/src/features/settings/ConfirmSheet.tsxweb/mobile/src/features/settings/CustomEndpointSheet.tsxweb/mobile/src/features/settings/Field.tsxweb/mobile/src/features/settings/LlmProvidersTab.tsxweb/mobile/src/features/settings/MembersTab.tsxweb/mobile/src/features/settings/PlanChooserSheet.tsxweb/mobile/src/features/settings/ProjectsTab.tsxweb/mobile/src/features/settings/ProviderKeySheet.tsxweb/mobile/src/features/settings/SecretFormSheet.tsxweb/mobile/src/features/settings/SecretsTab.tsxweb/mobile/src/features/settings/SettingsScreen.tsxweb/mobile/src/features/settings/WebhookFormSheet.tsxweb/mobile/src/features/settings/WebhooksTab.tsxweb/mobile/src/features/settings/settingsTabs.tsweb/mobile/src/features/settings/useConfirmSheet.tsxweb/oss/src/components/AgentChatSlice/assets/pageTitle.tsweb/oss/src/components/DrillInView/OSSdrillInUIProvider.tsxweb/oss/src/components/PageTitle/index.tsxweb/packages/agenta-chat/package.jsonweb/packages/agenta-chat/src/state/expandState.tsweb/packages/agenta-chat/src/state/sessionMessages.tsweb/packages/agenta-entities/src/drive/agentDrive.tsweb/packages/agenta-entities/src/drive/configDrive.tsweb/packages/agenta-entities/src/drive/driveMedia.tsweb/packages/agenta-entities/src/drive/useDriveSelection.tsweb/packages/agenta-entities/src/event/state/selectors.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolActionDetail.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolCatalogActions.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolConnectionQuery.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolIntegrationConnections.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolIntegrationDetail.tsweb/packages/agenta-entities/src/gatewayTool/prompt/atoms.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerCatalogEvents.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerConnections.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerDeliveries.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerEvent.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSchedule.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscription.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscriptions.tsweb/packages/agenta-entities/src/session/state/fileActivity.tsweb/packages/agenta-entities/src/session/state/mounts.tsweb/packages/agenta-entities/src/session/state/records.tsweb/packages/agenta-entities/src/webhook/atoms.tsweb/packages/agenta-entities/src/workflow/relations.tsweb/packages/agenta-entities/src/workflow/state/inspectMeta.tsweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentOperationsSections.tsxweb/packages/agenta-entity-ui/src/DrillInView/bridges/useWorkflowReferenceBridge.tsweb/packages/agenta-entity-ui/src/DrillInView/index.tsweb/packages/agenta-entity-ui/src/agent/state.tsweb/packages/agenta-entity-ui/src/drive/SessionFilesDrawer.tsxweb/packages/agenta-entity-ui/src/drive/quickLook.tsxweb/packages/agenta-entity-ui/src/secretProvider/CustomProviderForm.tsxweb/packages/agenta-navigation/package.jsonweb/packages/agenta-navigation/src/state.tsweb/packages/agenta-observability/package.jsonweb/packages/agenta-observability/src/state/index.tsweb/packages/agenta-playground-ui/src/components/WorkflowRevisionDrawer/DrawerContent.tsxweb/packages/agenta-playground/src/state/execution/channelMode.tsweb/packages/agenta-sessions/package.jsonweb/packages/agenta-sessions/src/state/tabOrder.tsweb/packages/agenta-settings-ui/package.jsonweb/packages/agenta-settings-ui/src/tools/hooks/useIntegrationDetail.tsweb/packages/agenta-shared/package.jsonweb/packages/agenta-shared/src/state/featureFlags.tsweb/packages/agenta-shared/src/state/simulatedAgentRun.tsweb/packages/agenta-shared/src/utils/index.tsweb/packages/agenta-shared/src/utils/pageTitle.tsweb/packages/agenta-shared/tests/unit/pageTitle.test.tsweb/packages/agenta-ui/src/components/ui/data-table.tsx
| useEffect(() => { | ||
| if (!open) return | ||
| setKey(provider?.key ?? "") | ||
| setError(null) | ||
| setSaving(false) | ||
| }, [open, provider]) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Both edit sheets seed a form field from a stored credential. The shared root cause is the reset effect: each sheet loads the persisted secret into a plain-text control instead of starting empty and treating an empty field as "keep the stored value". That either exposes a credential on screen or writes a masked value back to the vault. WebhookFormSheet.tsx Lines 90-93 already applies the empty-means-unchanged rule, so adopt it in both sheets.
web/mobile/src/features/settings/ProviderKeySheet.tsx#L34-L39: replacesetKey(provider?.key ?? "")withsetKey(""), and skip the key inhandleModifyVaultSecretwhen the field is empty.web/mobile/src/features/settings/SecretFormSheet.tsx#L70-L85: stop loadingsecret.contentintovaluewhensecretexists, relaxcanSubmitso an edit does not require the value, and omitcontentfrom the payload when the field is empty.
📍 Affects 2 files
web/mobile/src/features/settings/ProviderKeySheet.tsx#L34-L39(this comment)web/mobile/src/features/settings/SecretFormSheet.tsx#L70-L85
| // Destructive actions in the shared tool/trigger sections ask for confirmation through an | ||
| // imperative callback (the desktop hands them antd's AlertPopup); this is the sheet version. | ||
| const {confirm, sheet: confirmSheet} = useConfirmSheet() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Reset pending confirmations when tab changes.
If tab changes while a request is pending, TabBody remains mounted and useConfirmSheet keeps the request. The sheet is rendered in the tools and triggers cases, so returning can reopen the old destructive action. Reset the request on tab changes, or remount TabBody per tab.
Also applies to: 189-202
| case "tools": | ||
| return <GatewayToolsSection readOnly /> | ||
| return ( | ||
| <> | ||
| <GatewayToolsSection confirm={confirm} /> | ||
| {confirmSheet} | ||
| </> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 \
'\bGatewayToolsSection\b|\buseMobileSettingsAccess\b|tools.*(access|gate)|((access|gate).*tools)' \
web/mobile web/packagesRepository: Agenta-AI/agenta
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- SettingsScreen ---'
sed -n '1,230p' web/mobile/src/features/settings/SettingsScreen.tsx
printf '%s\n' '--- settingsTabs ---'
sed -n '1,260p' web/mobile/src/features/settings/settingsTabs.tsx
printf '%s\n' '--- GatewayToolsSection ---'
sed -n '1,300p' web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx
printf '%s\n' '--- access-related definitions and uses ---'
rg -n -C 5 'useMobileSettingsAccess|canShowTools|readOnly|GatewayToolsSection' \
web/mobile/src/features/settings web/packages/agenta-settings-ui/src/toolsRepository: Agenta-AI/agenta
Length of output: 9065
🏁 Script executed:
#!/bin/bash
set -euo pipefail
settings_tabs="$(fd -i '^settingsTabs\.' web/mobile/src/features/settings)"
printf '%s\n' "--- settingsTabs candidates: ${settings_tabs} ---"
for file in $settings_tabs; do
sed -n '1,280p' "$file"
done
printf '%s\n' '--- remaining SettingsScreen ---'
sed -n '230,520p' web/mobile/src/features/settings/SettingsScreen.tsx
printf '%s\n' '--- GatewayToolsSection ---'
sed -n '1,360p' web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx
printf '%s\n' '--- direct access and readOnly references ---'
rg -n -C 6 'useMobileSettingsAccess|canShowTools|readOnly|GatewayToolsSection' \
web/mobile/src/features/settings web/packages/agenta-settings-ui/src/toolsRepository: Agenta-AI/agenta
Length of output: 45583
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
screen = Path("web/mobile/src/features/settings/SettingsScreen.tsx").read_text()
tabs = Path("web/mobile/src/features/settings/settingsTabs.ts").read_text()
section = Path("web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx").read_text()
checks = {
"tools is an accepted direct-query tab": '"tools"' in tabs and "AVAILABLE_SETTINGS_TABS.includes(requested as SettingsTabKey)" in tabs,
"direct-query resolution does not apply canShowTools": 'return AVAILABLE_SETTINGS_TABS.includes(requested as SettingsTabKey)' in tabs,
"tools body renders GatewayToolsSection": '<GatewayToolsSection confirm={confirm} />' in screen,
"tools body does not pass readOnly": '<GatewayToolsSection confirm={confirm} readOnly=' not in screen,
"GatewayToolsSection defaults readOnly to false": 'function GatewayToolsSection({confirm, readOnly}: GatewayToolsSectionProps)' in section,
"connect action is hidden only by readOnly": 'hidden: readOnly' in section and '{readOnly ? null : <CatalogDrawer' in section,
}
for name, result in checks.items():
print(f"{'PASS' if result else 'FAIL'}: {name}")
if not all(checks.values()):
raise SystemExit(1)
PY
printf '%s\n' '--- shared feature helper and desktop access ---'
rg -n -C 8 'function isToolsEnabled|const isToolsEnabled|isToolsEnabled|useSettingsAccess|canShowTools' \
web/packages web/ee web/oss 2>/dev/null || trueRepository: Agenta-AI/agenta
Length of output: 26653
Enforce the tools access gate at the render boundary. Direct ?tab=tools navigation bypasses canShowTools, and GatewayToolsSection renders writable controls because readOnly is unset. Do not render this section when access.canShowTools is false.
| setSaving(false) | ||
| setName(editing?.name ?? "") | ||
| setUrl(editing?.data?.url ?? "") | ||
| setEventType(editing?.data?.event_types?.[0] ?? EVENT_OPTIONS[0].value) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Editing a subscription discards every event type except the first.
Line 63 loads only event_types[0]. Line 78 writes event_types: [eventType]. If a subscription was created elsewhere with several event types, a save from this sheet drops the rest without warning. Today EVENT_OPTIONS holds one entry, so the sheet cannot restore them.
Preserve the unedited types, or block editing when the stored list holds more than one type.
🛡️ Proposed fix: keep the types the sheet cannot represent
const [eventType, setEventType] = useState<WebhookEventType>(EVENT_OPTIONS[0].value)
+ const [extraEventTypes, setExtraEventTypes] = useState<WebhookEventType[]>([])
@@
setEventType(editing?.data?.event_types?.[0] ?? EVENT_OPTIONS[0].value)
+ setExtraEventTypes(editing?.data?.event_types?.slice(1) ?? [])
@@
const data = {
url: url.trim(),
- event_types: [eventType],
+ event_types: [eventType, ...extraEventTypes],
auth_mode: authMode,
}Also applies to: 76-80
| // A signed subscription mints a secret the backend returns exactly once. | ||
| const secret = created.subscription?.secret || created.subscription?.secret_id | ||
| if (authMode === "signature" && secret) setCreatedSecret(secret) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the create-webhook response shape and how the desktop reads the one-time secret.
rg -n --glob '!**/node_modules/**' -C5 -e 'secret_id' -e 'createdWebhookSecretAtom' web/packages web/oss
fd -t f 'atoms.ts' web/packages/agenta-entities/src/webhook --exec cat -n {}Repository: Agenta-AI/agenta
Length of output: 40710
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- mobile form ---'
sed -n '70,135p' web/mobile/src/features/settings/WebhookFormSheet.tsx
printf '%s\n' '--- mobile consumers ---'
rg -n -C6 'createdWebhookSecretAtom|createdWebhookSecret|secret_id|secret' web/mobile/src/features/settings web/mobile/src
printf '%s\n' '--- webhook entity/API definitions ---'
sed -n '1,120p' web/packages/agenta-entities/src/webhook/types.ts
rg -n -C8 'createWebhookSubscription|WebhookSubscription' web/packages/agenta-entities/src/webhook web/packages/agenta-api-client/src/generated/api/resources/webhooks web/packages/agenta-api-client/src/generated/api/types
printf '%s\n' '--- backend webhook implementation references ---'
rg -n -C6 'secret_id|secret.*signature|signature.*secret|WebhookSubscription' backend web --glob '!**/node_modules/**' --glob '!web/packages/agenta-api-client/src/generated/**' | head -n 500Repository: Agenta-AI/agenta
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
p = Path("web/mobile/src/features/settings/WebhookFormSheet.tsx")
text = p.read_text()
needle = "created.subscription?.secret || created.subscription?.secret_id"
print("fallback_present:", needle in text)
print("secret_only_present:", "const secret = created.subscription?.secret" in text)
PYRepository: Agenta-AI/agenta
Length of output: 203
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- webhook-related files ---'
git ls-files | rg -i 'webhook|subscription' | head -n 200
printf '%s\n' '--- generated response and request types ---'
rg -l 'interface WebhookSubscription|type WebhookSubscription|secret_id' web/packages/agenta-api-client/src/generated/api | head -n 50
for f in $(rg -l 'interface WebhookSubscription|type WebhookSubscription' web/packages/agenta-api-client/src/generated/api | head -n 10); do
echo "--- $f"
cat -n "$f"
done
printf '%s\n' '--- webhook API wrappers ---'
rg -n -C8 'createWebhookSubscription|createWebhook' web/packages/agenta-entities/src/webhook web/packages/agenta-entities/src --glob '*.ts'
printf '%s\n' '--- mobile secret reveal block ---'
sed -n '25,120p' web/mobile/src/features/settings/WebhooksTab.tsxRepository: Agenta-AI/agenta
Length of output: 36842
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
form = Path("web/mobile/src/features/settings/WebhookFormSheet.tsx").read_text()
tab = Path("web/mobile/src/features/settings/WebhooksTab.tsx").read_text()
checks = {
"fallback_uses_secret_id": "created.subscription?.secret || created.subscription?.secret_id" in form,
"only_secret_is_rendered": "{createdSecret}" in tab,
"success_runs_without_secret_error": "onSuccess()" in form and "setError(" in form,
"secret_modal_requires_atom": "createdSecret" in tab and "Save your webhook secret" in tab,
}
for name, value in checks.items():
print(f"{name}: {value}")
PYRepository: Agenta-AI/agenta
Length of output: 287
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- exact generated webhook model ---'
f=$(rg -l 'interface WebhookSubscription' web/packages/agenta-api-client/src/generated/api | head -n 1)
test -n "$f" && { echo "$f"; cat -n "$f"; }
printf '%s\n' '--- exact webhook entity types and API ---'
cat -n web/packages/agenta-entities/src/webhook/types.ts | sed -n '1,90p'
cat -n web/packages/agenta-entities/src/webhook/api.ts 2>/dev/null || true
cat -n web/packages/agenta-entities/src/webhook/api/api.ts 2>/dev/null || true
printf '%s\n' '--- form setup and completion flow ---'
sed -n '1,75p' web/mobile/src/features/settings/WebhookFormSheet.tsx
sed -n '90,125p' web/mobile/src/features/settings/WebhooksTab.tsx
printf '%s\n' '--- backend candidates containing webhook route/schema ---'
rg -l -i 'webhook' --glob '!web/packages/agenta-api-client/src/generated/**' --glob '!**/node_modules/**' | head -n 100Repository: Agenta-AI/agenta
Length of output: 12652
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- backend webhook models and secret handling ---'
cat -n api/oss/src/apis/fastapi/webhooks/models.py | sed -n '1,240p'
cat -n api/oss/src/core/webhooks/types.py | sed -n '1,260p'
rg -n -C10 'secret_id|secret\b|auth_mode' api/oss/src/apis/fastapi/webhooks api/oss/src/core/webhooks api/oss/tests/pytest/acceptance/webhooks api/oss/tests/pytest/unit/webhooks
printf '%s\n' '--- API docs for create response ---'
rg -n -C8 'secret_id|secret' docs/docs/reference/api/create-webhook-subscription* clients/python/agenta_client/types/webhook_subscription*.pyRepository: Agenta-AI/agenta
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
paths = [
Path("api/oss/src/apis/fastapi/webhooks/models.py"),
Path("api/oss/src/core/webhooks/types.py"),
Path("web/mobile/src/features/settings/WebhookFormSheet.tsx"),
]
for path in paths:
text = path.read_text()
print(path)
for term in ("secret_id", "secret", "auth_mode", "onSuccess()", "close()"):
print(f" {term}: {text.count(term)}")
PYRepository: Agenta-AI/agenta
Length of output: 504
Use only secret as the webhook secret.
secret_id identifies the stored secret. It is not the signing secret. If secret is absent, show an error and do not continue to onSuccess() or close(). Otherwise, WebhooksTab.tsx displays an unusable identifier for signature verification.
| <Button | ||
| onClick={async () => { | ||
| if (!createdSecret) return | ||
| await navigator.clipboard?.writeText(createdSecret) | ||
| setCopied(true) | ||
| }} | ||
| > | ||
| {copied ? "Copied" : "Copy secret"} | ||
| </Button> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
The copy button reports success even when nothing was copied.
Two problems exist on Line 97:
- If
navigator.clipboardis undefined, for example in a non-secure context, the optional chain returnsundefined.await undefinedresolves, and Line 98 sets the label to "Copied". - If
writeTextrejects, for example because the document lost focus or permission was denied, the rejection is unhandled and the user sees no error.
The secret appears exactly once, so a false "Copied" makes the user lose it. Handle both paths and keep the sheet open on failure.
🐛 Proposed fix
+ const [copyError, setCopyError] = useState<string | null>(null)
@@
<Button
onClick={async () => {
if (!createdSecret) return
- await navigator.clipboard?.writeText(createdSecret)
- setCopied(true)
+ try {
+ if (!navigator.clipboard) {
+ throw new Error("Clipboard access is unavailable.")
+ }
+ await navigator.clipboard.writeText(createdSecret)
+ setCopyError(null)
+ setCopied(true)
+ } catch {
+ setCopyError("Could not copy. Select the secret and copy it manually.")
+ }
}}
>
{copied ? "Copied" : "Copy secret"}
</Button>Render copyError above the footer, in the same style the other sheets use for errors.
| "@ai-sdk/react": ">=3.0.0-beta.0", | ||
| "ai": ">=6.0.0-beta.0", | ||
| "jotai": ">=2.0.0", | ||
| "jotai-family": ">=0.1.0", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path
for path in Path(".").rglob("package.json"):
if "node_modules" in path.parts or ".git" in path.parts:
continue
data = json.loads(path.read_text())
deps = {}
for section in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
deps.update(data.get(section, {}))
if "`@agenta/chat`" in deps or "`@agenta/sessions`" in deps:
if "jotai-family" not in deps:
print(f"Missing jotai-family declaration: {path}")
PYRepository: Agenta-AI/agenta
Length of output: 308
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
for path in sorted(Path("web").rglob("package.json")):
if "node_modules" in path.parts:
continue
data = json.loads(path.read_text())
name = data.get("name")
refs = {}
for section in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
for dep, version in data.get(section, {}).items():
if dep in ("`@agenta/chat`", "`@agenta/sessions`", "jotai-family"):
refs.setdefault(section, {})[dep] = version
if refs or name in ("`@agenta/chat`", "`@agenta/sessions`"):
print(f"\n{path}")
print(json.dumps({"name": name, **refs}, indent=2, sort_keys=True))
print("\nSource imports of jotai-family:")
for path in sorted(Path("web").rglob("*")):
if not path.is_file() or "node_modules" in path.parts:
continue
try:
text = path.read_text()
except (UnicodeDecodeError, OSError):
continue
if "jotai-family" in text:
print(path)
PYRepository: Agenta-AI/agenta
Length of output: 10799
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' 'Consumer package manifests:'
sed -n '1,90p' web/packages/agenta-home-ui/package.json
sed -n '1,70p' web/packages/agenta-sessions-ui/package.json
printf '%s\n' 'Consumer source imports:'
rg -n --glob '!node_modules/**' --glob '!package.json' \
'(`@agenta/`(chat|sessions)|agenta-chat|agenta-sessions)' \
web/packages/agenta-home-ui web/packages/agenta-sessions-ui || true
printf '%s\n' 'Provider source imports:'
rg -n --glob '!node_modules/**' 'from ["'\'']jotai-family["'\'']|require\(["'\'']jotai-family["'\'']\)' \
web/packages/agenta-chat web/packages/agenta-sessionsRepository: Agenta-AI/agenta
Length of output: 6083
Declare jotai-family in both consumer packages.
@agenta/home-ui and @agenta/sessions-ui consume packages that require jotai-family as a peer dependency. Add jotai-family to both package manifests.
📍 Affects 2 files
web/packages/agenta-chat/package.json#L43-L43(this comment)web/packages/agenta-sessions/package.json#L30-L30
| sticky ? "sticky top-0 z-[10]" : "" | ||
| } w-full border-b border-colorBorderSecondary py-2 px-4 bg-[var(--ag-c-FFFFFF)] bg-[image:linear-gradient(var(--ant-color-fill-tertiary),var(--ant-color-fill-tertiary))]` | ||
| const titleClass = "text-[13px] font-semibold text-[var(--ant-color-text)]" | ||
| } w-full border-b border-colorBorderSecondary py-2 px-4 bg-[var(--ag-c-FFFFFF)] bg-[image:linear-gradient(var(--ag-colorFillTertiary),var(--ag-colorFillTertiary))]` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace the legacy color literal.
Line 30 uses --ag-c-FFFFFF, which the theme rules prohibit. Use a semantic background token such as bg-colorBgContainer for the fallback color.
Proposed fix
- } w-full border-b border-colorBorderSecondary py-2 px-4 bg-[var(--ag-c-FFFFFF)] bg-[image:linear-gradient(var(--ag-colorFillTertiary),var(--ag-colorFillTertiary))]`
+ } w-full border-b border-colorBorderSecondary py-2 px-4 bg-colorBgContainer bg-[image:linear-gradient(var(--ag-colorFillTertiary),var(--ag-colorFillTertiary))]`📝 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.
| } w-full border-b border-colorBorderSecondary py-2 px-4 bg-[var(--ag-c-FFFFFF)] bg-[image:linear-gradient(var(--ag-colorFillTertiary),var(--ag-colorFillTertiary))]` | |
| } w-full border-b border-colorBorderSecondary py-2 px-4 bg-colorBgContainer bg-[image:linear-gradient(var(--ag-colorFillTertiary),var(--ag-colorFillTertiary))]` |
Source: Coding guidelines
| "@tanstack/react-query": ">=5.0.0", | ||
| "axios": ">=1.13.5 <2.0.0", | ||
| "jotai": ">=2.0.0", | ||
| "jotai-family": ">=0.1.0", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path
peers = json.loads(
Path("web/packages/agenta-shared/package.json").read_text()
)["peerDependencies"]
print("shared jotai peer:", peers["jotai"])
print("shared jotai-family peer:", peers["jotai-family"])
PY
curl -fsSL https://registry.npmjs.org/jotai-family/1.0.1 \
| python -c 'import json, sys; print(json.load(sys.stdin).get("peerDependencies", {}))'
rg -n -A5 -B2 'jotai-family@1\.0\.1|jotai:.*2\.9\.0' web/pnpm-lock.yamlRepository: Agenta-AI/agenta
Length of output: 1928
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import json
import subprocess
from pathlib import Path
pkg = json.loads(Path("web/packages/agenta-shared/package.json").read_text())
print("package peerDependencies:", json.dumps(pkg.get("peerDependencies", {}), sort_keys=True))
print("package dependencies:", json.dumps(pkg.get("dependencies", {}), sort_keys=True))
lock = Path("web/pnpm-lock.yaml").read_text()
for needle in ("jotai-family@1.0.1:", "jotai-family@0.", "jotai-family@1.", "jotai-family:"):
print(f"\nLOCK MATCHES: {needle}")
lines = lock.splitlines()
for i, line in enumerate(lines):
if needle in line:
print("\n".join(f"{j+1}: {lines[j]}" for j in range(max(0, i-2), min(len(lines), i+8))))
print("\nJOTAI-FAMILY USAGES")
subprocess.run(
["rg", "-n", "--glob", "!pnpm-lock.yaml", r"jotai-family|atomFamily"],
check=False,
)
PY
curl -fsSL https://registry.npmjs.org/jotai-family \
| python3 - <<'PY'
import json, sys
data = json.load(sys.stdin)
for version in data.get("versions", {}):
if version.startswith(("0.", "1.")):
manifest = data["versions"][version]
print(version, json.dumps(manifest.get("peerDependencies", {}), sort_keys=True))
PYRepository: Agenta-AI/agenta
Length of output: 11257
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "JOTAI-FAMILY USAGES"
rg -n --glob '!pnpm-lock.yaml' 'jotai-family|atomFamily' web/packages/agenta-shared web/packages 2>/dev/null || true
echo
echo "JOTAI-FAMILY REGISTRY VERSIONS AND PEERS"
curl -fsSL https://registry.npmjs.org/jotai-family \
| python3 -c '
import json, sys
data = json.load(sys.stdin)
for version, manifest in data.get("versions", {}).items():
if version.startswith(("0.", "1.")):
print(version, json.dumps(manifest.get("peerDependencies", {}), sort_keys=True))
'
echo
echo "JOTAI-FAMILY VERSION METADATA"
for version in 0.1.0 0.2.0 0.2.6 1.0.0 1.0.1; do
printf "%s: " "$version"
curl -fsSL "https://registry.npmjs.org/jotai-family/$version" \
| python3 -c 'import json, sys; d=json.load(sys.stdin); print(json.dumps({"version": d.get("version"), "peerDependencies": d.get("peerDependencies", {}), "engines": d.get("engines", {})}, sort_keys=True))'
doneRepository: Agenta-AI/agenta
Length of output: 50372
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
curl -fsSL https://registry.npmjs.org/jotai-family \
| python3 -c '
import json, sys
data = json.load(sys.stdin)
for version, manifest in data.get("versions", {}).items():
if version.startswith(("0.", "1.")):
print(version, json.dumps(manifest.get("peerDependencies", {}), sort_keys=True))
'
echo
for version in 0.1.0 0.2.0 0.2.6 1.0.0 1.0.1; do
printf "%s: " "$version"
curl -fsSL "https://registry.npmjs.org/jotai-family/$version" \
| python3 -c '
import json, sys
d = json.load(sys.stdin)
print(json.dumps({
"version": d.get("version"),
"peerDependencies": d.get("peerDependencies", {}),
"engines": d.get("engines", {})
}, sort_keys=True))
'
doneRepository: Agenta-AI/agenta
Length of output: 961
Raise the Jotai peer floor to >=2.9.0.
jotai-family requires jotai >=2.9.0. The current jotai >=2.0.0 range permits unsupported Jotai versions. Keep jotai-family >=0.1.0; published versions before 1.0.0 do not satisfy this range.
| // The empty state carries its own call to action, and on a phone the two sit far enough | ||
| // apart to read as different controls — so show only that one. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Reduce this source comment to one short line.
The condition already shows the mobile behavior. Keep only a short statement if the comment is needed.
As per coding guidelines, keep in-code comments to at most one short line unless they document a genuinely surprising constraint.
Source: Coding guidelines
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: 12
🧹 Nitpick comments (6)
web/packages/agenta-entity-ui/src/DrillInView/bridges/useWorkflowReferenceBridge.ts (2)
537-590: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDeduplicate the three
retrieveWorkflowRevisioncalls per workflow.
resolveInputSchema,resolveOutputSchema, andresolveConfigPayloadeach fetch the same revision by slug. When the reference drawer opens, this produces three identical requests, and the three results can disagree if a commit lands between them.Cache the revision per
projectId+slug, for example through a shared promise map or a TanStack QueryfetchQuery, and have all three resolvers read it.
46-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winImport
atomFamilyfromjotai-family.This file still imports it from
jotai/utils, while the package usesjotai-familyelsewhere. The package already declaresjotai-familyas a peer dependency.web/mobile/src/components/PageTitle.tsx (1)
4-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShorten the in-code documentation.
This four-line comment duplicates the formatter contract. Replace it with one short line, or move the full behavior description to external documentation.
As per coding guidelines, keep in-code comments to at most one short line unless they document a genuinely surprising constraint.
Source: Coding guidelines
web/mobile/src/features/settings/LlmProvidersTab.tsx (1)
17-19: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueBoth provider tables share one
deletinganderrorstate.
deleteDialogreads the sameerrorfor the standard table and the custom table. If a delete fails in one table, and the user then opens the delete confirm in the other table, the stale message appears. The table controlsopendirectly, soonClosedoes not run first.Key the state by provider id, or reset
errorwhenselectedProviderchanges.Also applies to: 21-62
web/mobile/src/features/settings/SecretFormSheet.tsx (1)
191-202: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract the
textareastyling into a shared component.The class list on Line 201 restates the
Inputcomponent's border, focus-ring, and dark-mode rules. A copy drifts fromInputthe next time the input styling changes. Add aTextareacomponent next to@/components/ui/inputand use it here.web/mobile/src/features/settings/useConfirmSheet.tsx (1)
5-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLet the caller set the confirm label and the destructive style.
ConfirmSheetdefaultsdestructivetotrue, and this hook always passesconfirmLabel="Confirm". Every confirmation from the shared sections therefore shows a red generic button, including a non-destructive one. Add optionalconfirmLabelanddestructivefields toConfirmRequestand forward them.Also applies to: 32-54
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 16641e06-c39c-4abc-b4b5-39b81fd6cd91
⛔ Files ignored due to path filters (1)
web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (87)
web/mobile/next.config.tsweb/mobile/package.jsonweb/mobile/src/components/PageTitle.tsxweb/mobile/src/components/ui/sheet.tsxweb/mobile/src/features/agents/AgentListScreen.tsxweb/mobile/src/features/agents/AgentOverviewScreen.tsxweb/mobile/src/features/agents/AgentTemplateDetailScreen.tsxweb/mobile/src/features/agents/AgentTemplatesScreen.tsxweb/mobile/src/features/chat/ConfigPane.tsxweb/mobile/src/features/chat/DrillInBridgeProvider.tsxweb/mobile/src/features/chat/SessionTabs.tsxweb/mobile/src/features/chat/selectedRevision.tsweb/mobile/src/features/context/ContextResolver.tsxweb/mobile/src/features/home/HomeScreen.tsxweb/mobile/src/features/nav/useMobileNavItems.tsxweb/mobile/src/features/sessions/SessionListScreen.tsxweb/mobile/src/features/settings/AccountTab.tsxweb/mobile/src/features/settings/CancelSubscriptionSheet.tsxweb/mobile/src/features/settings/ConfirmSheet.tsxweb/mobile/src/features/settings/CustomEndpointSheet.tsxweb/mobile/src/features/settings/Field.tsxweb/mobile/src/features/settings/LlmProvidersTab.tsxweb/mobile/src/features/settings/MembersTab.tsxweb/mobile/src/features/settings/PlanChooserSheet.tsxweb/mobile/src/features/settings/ProjectsTab.tsxweb/mobile/src/features/settings/ProviderKeySheet.tsxweb/mobile/src/features/settings/SecretFormSheet.tsxweb/mobile/src/features/settings/SecretsTab.tsxweb/mobile/src/features/settings/SettingsScreen.tsxweb/mobile/src/features/settings/WebhookFormSheet.tsxweb/mobile/src/features/settings/WebhooksTab.tsxweb/mobile/src/features/settings/settingsTabs.tsweb/mobile/src/features/settings/useConfirmSheet.tsxweb/oss/src/components/AgentChatSlice/assets/pageTitle.tsweb/oss/src/components/DrillInView/OSSdrillInUIProvider.tsxweb/oss/src/components/PageTitle/index.tsxweb/packages/agenta-chat/package.jsonweb/packages/agenta-chat/src/state/expandState.tsweb/packages/agenta-chat/src/state/sessionMessages.tsweb/packages/agenta-entities/src/drive/agentDrive.tsweb/packages/agenta-entities/src/drive/configDrive.tsweb/packages/agenta-entities/src/drive/driveMedia.tsweb/packages/agenta-entities/src/drive/useDriveSelection.tsweb/packages/agenta-entities/src/event/state/selectors.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolActionDetail.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolCatalogActions.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolConnectionQuery.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolIntegrationConnections.tsweb/packages/agenta-entities/src/gatewayTool/hooks/useToolIntegrationDetail.tsweb/packages/agenta-entities/src/gatewayTool/prompt/atoms.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerCatalogEvents.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerConnections.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerDeliveries.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerEvent.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSchedule.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscription.tsweb/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSubscriptions.tsweb/packages/agenta-entities/src/session/state/fileActivity.tsweb/packages/agenta-entities/src/session/state/mounts.tsweb/packages/agenta-entities/src/session/state/records.tsweb/packages/agenta-entities/src/webhook/atoms.tsweb/packages/agenta-entities/src/workflow/relations.tsweb/packages/agenta-entities/src/workflow/state/inspectMeta.tsweb/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentOperationsSections.tsxweb/packages/agenta-entity-ui/src/DrillInView/bridges/useWorkflowReferenceBridge.tsweb/packages/agenta-entity-ui/src/DrillInView/index.tsweb/packages/agenta-entity-ui/src/agent/state.tsweb/packages/agenta-entity-ui/src/drive/SessionFilesDrawer.tsxweb/packages/agenta-entity-ui/src/drive/quickLook.tsxweb/packages/agenta-entity-ui/src/secretProvider/CustomProviderForm.tsxweb/packages/agenta-navigation/package.jsonweb/packages/agenta-navigation/src/state.tsweb/packages/agenta-observability/package.jsonweb/packages/agenta-observability/src/state/index.tsweb/packages/agenta-playground-ui/src/components/WorkflowRevisionDrawer/DrawerContent.tsxweb/packages/agenta-playground/src/state/execution/channelMode.tsweb/packages/agenta-sessions/package.jsonweb/packages/agenta-sessions/src/state/tabOrder.tsweb/packages/agenta-settings-ui/package.jsonweb/packages/agenta-settings-ui/src/tools/hooks/useIntegrationDetail.tsweb/packages/agenta-shared/package.jsonweb/packages/agenta-shared/src/state/featureFlags.tsweb/packages/agenta-shared/src/state/simulatedAgentRun.tsweb/packages/agenta-shared/src/utils/index.tsweb/packages/agenta-shared/src/utils/pageTitle.tsweb/packages/agenta-shared/tests/unit/pageTitle.test.tsweb/packages/agenta-ui/src/components/ui/data-table.tsx
🛑 Comments failed to post (1)
web/packages/agenta-shared/src/utils/pageTitle.ts (1)
7-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Handle non-positive
maxLengthbefore adding the ellipsis.
truncateTitlePartreturns"…"whenmaxLengthis0or negative and the normalized value is not empty. The result exceeds the requested limit. Return an empty string or reject invalid limits before truncation. Add a regression test for0.Proposed fix
const normalized = normalizeTitlePart(value).replace(/[\uD800-\uDBFF]$/, "") + if (maxLength <= 0) return "" const characters = Array.from(normalized)📝 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.export const truncateTitlePart = (value: string, maxLength: number): string => { // Server-persisted titles may arrive cut on a UTF-16 boundary; a trailing high surrogate // has no pair left and renders as a replacement character. const normalized = normalizeTitlePart(value).replace(/[\uD800-\uDBFF]$/, "") if (maxLength <= 0) return "" const characters = Array.from(normalized) if (characters.length <= maxLength) return normalized return `${characters .slice(0, Math.max(0, maxLength - 1)) .join("") .trimEnd()}…` }
The previous commit claimed this and did not do it. The settings feature had been refactored in the meantime — the access object moved to settingsTabs.ts — so the string replace matched nothing and no-op'd silently, and lint/tsc stayed green because nothing was broken, only unchanged. canShowTools/canShowTriggers now come from isToolsEnabled(), and isEE from the shared isEE() rather than a bare `=== "ee"` that misses the cloud tiers. settingsTabs.ts is the only place mobile builds SettingsAccess, so this is the whole surface.
readOnly was a guard against the create/edit drawers rendering antd forms with no ConfigProvider here. That reason is gone — SchemaForm and SubscriptionForm moved to @rc-component/form, so the drawers carry no antd theming. Destructive actions in those sections ask for confirmation through an imperative callback (the desktop passes antd's AlertPopup). useConfirmSheet adapts the existing ConfirmSheet, which is controlled, to that shape, and reports a failed action in the sheet rather than swallowing it — this app has no toasts. Subscriptions and schedules take no confirm: they delete through their own handlers, not through the shared prompt. Gates: lint 24/24, tsc 0 on mobile. Not yet exercised in a browser — the drawers and the confirm path are the things to try.
A section with a title but no search rendered the heading, then the buttons on their own row below — a band of empty space between them, and the buttons sitting closer to the table than to the section they act on. The three Triggers sections all read that way. The actions now ride the title's row when there is one, and fall back to the end of the filter row when there is not (Tools, which searches and has no title, is unchanged). A section with both gets the actions beside the title and the search on its own row, since the search box is too wide to share. Aligned to the top of the title block, which is two lines — name over description. Gates: lint 24/24, tsc 0 across ui, settings-ui, oss and mobile.
Four narrow-viewport fixes, all in the primitive so every settings tab gets them: The header row stacks below sm instead of wrapping. `ml-auto` meant the buttons landed hard-right once they wrapped, leaving a gap under the description — they now span the row. The empty state's call to action is the only one shown on a phone. The toolbar carries the same button, and at this width the two sit far enough apart to read as different controls. Checked every caller first: the CTA-less empty states are all search-no-match ones, where the search box is right there, so nothing is left without an action. Search goes full-width below sm; a fixed 260px overflowed. The empty region loses some height on narrow — three stacked sections were mostly whitespace before reaching content. Not fixed here: the table still compresses rather than scrolling, because it is `w-full` with no min-width. Five columns in 360px is the real problem and wants a card-per-row layout below sm — its own change. Gates: lint 24/24, tsc 0 across ui, settings-ui, oss and mobile.
A full row for one icon button is too much. Reload now stays beside the section title at every width; only the primary button — wide, labelled — wraps to its own full-width row below sm, and rejoins the line from sm up. With the empty state hiding the primary button on a phone, the header collapses to a single line: title, description, reload. Only this file is staged — the other changes in the tree are another agent's work in flight (mobile's tsc is failing on jotai-family resolution and some nav/chat edits that have nothing to do with this).
…entity-ui OSSdrillInUIProvider carried 588 lines of workflow-reference wiring that was not OSS-specific — /m needs the same behaviour to render a config pane, and could not reach it. It becomes useWorkflowReferenceBridge in the package, and the provider keeps only the host's own composition. The bridge imports nothing from @/oss, so it holds in a host that cannot see the app layer. Lane: this belongs with the entity-ui work, above pkg/session-surfaces — see docs/design/sessions-ux-stack/restack-onto-112.md.
Consumes useWorkflowReferenceBridge through a 20-line provider, so /m gets the reference behaviour the desktop has instead of a second implementation. Lane: mobile, and it depends on the bridge commit below it.
…eclare it Six packages imported `atomFamily` from `jotai/utils` while resolving it through a transitive copy — @agenta/mobile was failing tsc on `Cannot find module 'jotai-family'` because nothing in its graph declared the dep. Every call site now imports from `jotai-family`, and each package that uses it lists it. 46 files, one behavioural change: none. Lane: this is package-wide plumbing and belongs below the settings lanes — see docs/design/sessions-ux-stack/restack-onto-112.md.
The title rules — normalize, truncate on a character boundary, join the parts — lived in oss/components/PageTitle/utils and /m had its own `parts` array doing roughly the same thing. They now share one implementation, with its test. The surrogate-pair guard is the reason this is worth sharing rather than reimplementing: a server-persisted session title can arrive cut mid-pair, and the trailing high surrogate renders as a replacement character. Each host's PageTitle is now just the <title> element for its router. Lane: @agenta/shared, below everything else; the oss and mobile rewires ride with it since the old util is deleted in the same change.
The SDK guards those imports with try/catch, so webpack resolving them is noise. Production only — dev runs Turbopack.
Both edit sheets loaded the persisted credential into a plain-text control: `ProviderKeySheet` from `provider.key`, `SecretFormSheet` from `secret.content`. The vault returns both in the clear — nothing masks them server-side — so opening either sheet put a live credential on screen. Start empty in both. The provider key is the value to write, so a save always replaces what is stored and an empty field saves nothing (the vault's standard-secret write has no "key unchanged" form). A named secret can be renamed without retyping its value: empty on edit means keep the stored one, except when the format changed, since text and a JSON map are not convertible.
… is the secret Four defects in the subscribe/edit flow: - Editing loaded only `event_types[0]` and wrote back a single-element list, so a subscription created elsewhere with several types lost the rest on any save. The types the sheet has no control for now ride along untouched, and the field says how many. - The one-time reveal fell back to `secret_id` when `secret` was absent. `secret_id` names the stored record and verifies no signature, so the user would have signed against an identifier and failed. Only `secret` is used; a signature subscription that comes back without one says so instead. - Submit only required a non-empty URL. `type="url"` validates nothing outside a submitting form, so `example.com` saved and failed on delivery. - The copy button reported "Copied" unconditionally: `navigator.clipboard` is undefined outside a secure context and `writeText` rejects on denied permission. The secret is shown once, so a false "Copied" loses it.
…utlive a tab `useActiveSettingsTab` accepted any tab this app has a page for, without asking whether the viewer may see it. Both rails already hide what `isSettingsTabVisible` denies, so a typed `?tab=tools` was the one way in — and it landed on writable tool and trigger controls. The router now applies the same predicate the rails do, and the two cases check it again at the render boundary. The confirm sheet is now scoped to the open tab. `TabBody` stays mounted across tabs, so a destructive action staged on one was still pending on the next and could be confirmed against a screen the user had left.
…rface token `--ag-c-FFFFFF` is the legacy hex-named literal; `bg-colorBgContainer` is the role. Same value in both themes, so nothing moves.
`jotai-family@1` declares `jotai >=2.9.0`. The three packages that pair the two allowed `jotai >=2.0.0`, a range whose lower half the dependency does not support. The lockfile records importer peer specifiers, so it needs regenerating.
9ceda16 to
6f9fe71
Compare
4fd0ddd to
bcc6046
Compare
…it needs jotai-family@1.0.1 requires jotai >=2.9.0; twelve packages import it while nine still advertised >=2.0.0. Raised those nine and regenerated the lockfile, so --frozen-lockfile stops failing on the importers' peer specifiers. agenta-annotation-ui keeps >=2.0.0 — it does not use jotai-family.
The top of the stack — what was left after the settings pages were all in packages.
DataTableat phone widths: actions sit on the title's row, headers work when narrow, and areload keeps the title on one line.
/mTools and Triggers become writable (the form-engine lane below is what allows it), and/mactually reads the tools gate rather than assuming it.
useWorkflowReferenceBridgemoves out of the OSS drill-in provider into@agenta/entity-ui, and/m's config pane runs on it.atomFamilycomes fromjotai-familyand the packages declare the dependency; page-titlecomposition moves into
@agenta/shared; the AI SDK's optional zod peer stops warning on themobile build.
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-ee-pages; review only this lane's diff.