From d6c922f52ae2e7d1ae04aea9138985286c176c8c Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 17 Aug 2026 15:47:37 -0400 Subject: [PATCH] fix: repair schedule dialog interaction and rework its form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent picker was unusable: ControlCombobox portals its popover to the body by default, which lands it outside the dialog's Radix focus trap. Clicks passed through it, it could not be tabbed into, and the trap fighting Ariakit for focus locked the page up on selection. The prop is documented for exactly this case — pass `portal={false}` and give the dialog `overflow-visible`, as ProjectButton already does. The time and day dropdowns defaulted the same way. Alongside that: - Extract the agent builder's instructions editor (special-variable menu plus expand-to-fullscreen) into a controlled `VariableEditor` and use it for the schedule prompt. Insertions now route through `onChange`, so react-hook-form sees them — the schedule PATCH is built from `dirtyFields`, and a `setValue` that skipped dirty tracking would have dropped an inserted variable silently. - Replace the hand-rolled frequency buttons with the shared `Radio`. They marked the selection with `bg-surface-hover` on an outline button whose hover is the same token, so the selected option was indistinguishable from a hovered one; `Radio` is also a real radiogroup rather than four `aria-pressed` toggles. - Wrap the fields in a real `
` and associate the footer button by id, so Enter submits. Group the frequency, day and time controls in fieldsets. - Add placeholders for name and prompt, match the textarea fill to the other fields, and label the hourly case as minutes past the hour. - Widen the dialog to `md:max-w-3xl` and pair name with agent so the form fits without scrolling on desktop. - Move scheduled chats below skills and above prompts in the side nav. The new dialog spec fails when the portal fix is reverted. --- .../SidePanel/Agents/Instructions.tsx | 203 ++-------- .../SidePanel/Schedules/ScheduleDialog.tsx | 367 ++++++++++-------- .../__tests__/ScheduleDialog.spec.tsx | 109 ++++++ client/src/components/Variables/Editor.tsx | 203 ++++++++++ client/src/components/Variables/index.ts | 1 + client/src/hooks/Nav/useSideNavLinks.ts | 40 +- client/src/locales/en/translation.json | 3 + e2e/specs/mock/schedules-execution.spec.ts | 2 +- 8 files changed, 579 insertions(+), 349 deletions(-) create mode 100644 client/src/components/SidePanel/Schedules/__tests__/ScheduleDialog.spec.tsx create mode 100644 client/src/components/Variables/Editor.tsx create mode 100644 client/src/components/Variables/index.ts diff --git a/client/src/components/SidePanel/Agents/Instructions.tsx b/client/src/components/SidePanel/Agents/Instructions.tsx index e4b0ae58f6..347ad0a1d8 100644 --- a/client/src/components/SidePanel/Agents/Instructions.tsx +++ b/client/src/components/SidePanel/Agents/Instructions.tsx @@ -1,185 +1,42 @@ -import { useState, useId } from 'react'; -import * as Menu from '@ariakit/react/menu'; -import { PlusCircle, Maximize2 } from 'lucide-react'; -import { specialVariables } from 'librechat-data-provider'; import { Controller, useFormContext } from 'react-hook-form'; -import { - Label, - Button, - OGDialog, - Textarea, - DropdownPopup, - OGDialogClose, - TooltipAnchor, - OGDialogTitle, - OGDialogHeader, - OGDialogContent, -} from '@librechat/client'; -import type { TSpecialVarLabel } from 'librechat-data-provider'; import type { AgentForm } from '~/common'; +import { VariableEditor } from '~/components/Variables'; import { useLocalize } from '~/hooks'; -interface VariableOption { - label: TSpecialVarLabel; - value: string; -} - -const variableOptions: VariableOption[] = Object.keys(specialVariables).map((key) => ({ - label: `com_ui_special_var_${key}` as TSpecialVarLabel, - value: `{{${key}}}`, -})); - export default function Instructions() { - const menuId = useId(); - const dialogMenuId = useId(); const localize = useLocalize(); - const methods = useFormContext(); - const { control, setValue, getValues } = methods; - - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [isDialogMenuOpen, setIsDialogMenuOpen] = useState(false); - const [isDialogOpen, setIsDialogOpen] = useState(false); - - const handleAddVariable = (label: TSpecialVarLabel, value: string) => { - const currentInstructions = getValues('instructions') || ''; - const spacer = currentInstructions.length > 0 ? '\n' : ''; - const prefix = localize(label); - setValue('instructions', currentInstructions + spacer + prefix + ': ' + value); - setIsMenuOpen(false); - setIsDialogMenuOpen(false); - }; - - const variableItems = variableOptions.map((option) => ({ - label: localize(option.label) || option.label, - onClick: () => handleAddVariable(option.label, option.value), - })); + const { control } = useFormContext(); return ( -
-
- -
- - - - } - items={variableItems} - menuId={menuId} - className="pointer-events-auto z-30" - /> - setIsDialogOpen(true)} - aria-label={localize('com_ui_expand_editor')} - className="h-7 w-7 p-0 text-text-secondary hover:bg-surface-secondary hover:text-text-primary" - > - - - } + ( +
+ + {error && ( + + {localize('com_ui_field_required')} + + )}
-
- ( - <> -