From 18069565d922a907cdb66c9c5905fd0715f53cde Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Wed, 20 May 2026 13:41:20 +0700 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20address=20QA=20findings=20(P0?= =?UTF-8?q?=E2=80=93P3,=20excluding=20password=20policy)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - providers/flow-provider: map "no rows in result set" to a friendly "Flow not found" toast with a stable id so repeated sibling-query errors no longer stack raw SQL into the UI - components/ui/sidebar: skip the global Ctrl+B shortcut when focus is in an input, textarea, select, or contenteditable — was hijacking the Tiptap Bold shortcut in the markdown editor - components/ui/sidebar: mark the mobile drawer with group + data-state "expanded" so `group-data-[state=...]` selectors collapse correctly and flow id badges stop rendering twice in the drawer - pages/templates/template: add aria-label + title to the icon-only Save button and mark the icon aria-hidden - pages/settings/settings-api-tokens: render the date-picker label with date-fns format "d MMM yyyy" to match the table's date format - pages/settings/settings-providers: drop the fixed Name column width so the Type column stays visible on narrow viewports; Badge truncates cleanly with whitespace-nowrap and shrink-0 on the provider icon - components/shared/confirmation-dialog: derive the title from `\${confirmText} \${itemType}` so dialogs surface "Delete template" instead of a generic "Confirm Action", and use the verb in the description for consistency Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/shared/confirmation-dialog.tsx | 12 ++++++-- frontend/src/components/ui/sidebar.tsx | 29 ++++++++++++++++--- .../pages/settings/settings-api-tokens.tsx | 6 +++- .../src/pages/settings/settings-providers.tsx | 16 ++++++---- frontend/src/pages/templates/template.tsx | 8 ++++- frontend/src/providers/flow-provider.tsx | 14 ++++++--- 6 files changed, 67 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/shared/confirmation-dialog.tsx b/frontend/src/components/shared/confirmation-dialog.tsx index da12862a..1ec2bacb 100644 --- a/frontend/src/components/shared/confirmation-dialog.tsx +++ b/frontend/src/components/shared/confirmation-dialog.tsx @@ -46,13 +46,19 @@ function ConfirmationDialog({ isOpen, itemName = 'this', itemType = 'item', - title = 'Confirm Action', + title, }: ConfirmationDialogProps) { const [isProcessing, setIsProcessing] = useState(false); + // Derive a contextual title from confirm verb + item type so callers don't + // see "Confirm Action" for a Delete prompt or a Save prompt. Explicit + // `title` always wins. + const verb = confirmText.trim(); + const resolvedTitle = title ?? (verb && verb !== 'Confirm' ? `${verb} ${itemType}` : 'Confirm Action'); + const defaultDescription = description || ( <> - Are you sure you want to perform this action on{' '} + Are you sure you want to {verb.toLowerCase() || 'perform this action on'}{' '} {itemName} {itemType}? ); @@ -102,7 +108,7 @@ function ConfirmationDialog({ > - {title} + {resolvedTitle} {defaultDescription} diff --git a/frontend/src/components/ui/sidebar.tsx b/frontend/src/components/ui/sidebar.tsx index 335ed4e7..efe1c335 100644 --- a/frontend/src/components/ui/sidebar.tsx +++ b/frontend/src/components/ui/sidebar.tsx @@ -94,9 +94,15 @@ function Sidebar({ {...props} > { const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) { - event.preventDefault(); - toggleSidebar(); + if (event.key !== SIDEBAR_KEYBOARD_SHORTCUT || !(event.metaKey || event.ctrlKey)) { + return; } + + const target = event.target as HTMLElement | null; + + if (target) { + const tag = target.tagName; + + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable) { + return; + } + } + + event.preventDefault(); + toggleSidebar(); }; window.addEventListener('keydown', handleKeyDown); diff --git a/frontend/src/pages/settings/settings-api-tokens.tsx b/frontend/src/pages/settings/settings-api-tokens.tsx index 30dacc84..2f9a7bbc 100644 --- a/frontend/src/pages/settings/settings-api-tokens.tsx +++ b/frontend/src/pages/settings/settings-api-tokens.tsx @@ -646,7 +646,11 @@ function SettingsAPITokens() { variant="outline" > - {field.value ? field.value.toLocaleDateString() : Pick date} + {field.value ? ( + format(field.value, 'd MMM yyyy', { locale: enUS }) + ) : ( + Pick date + )} [ { accessorKey: 'name', - cell: ({ row }) =>
{row.getValue('name')}
, + cell: ({ row }) =>
{row.getValue('name')}
, enableHiding: false, header: ({ column }) => ( ), + // Name flexes to fill remaining width — fixed `size` would push + // the Type column off-screen on narrow viewports (e.g. 375px). meta: { searchable: true }, - size: 400, }, { accessorKey: 'type', cell: ({ row }) => { const providerType = row.getValue('type') as ProviderType; const Icon = providerIcons[providerType]; + const label = providerTypes.find((p) => p.type === providerType)?.label || providerType; return ( - - {Icon && } - {providerTypes.find((p) => p.type === providerType)?.label || providerType} + + {Icon && } + {label} ); }, @@ -166,6 +171,7 @@ function SettingsProviders() { /> ), meta: { searchable: true }, + minSize: 110, size: 160, }, { diff --git a/frontend/src/pages/templates/template.tsx b/frontend/src/pages/templates/template.tsx index ee13fecf..4b35139c 100644 --- a/frontend/src/pages/templates/template.tsx +++ b/frontend/src/pages/templates/template.tsx @@ -746,6 +746,7 @@ function Template() { /> - {isSaving ? : } + {isSaving ? ( + + ) : ( + diff --git a/frontend/src/providers/flow-provider.tsx b/frontend/src/providers/flow-provider.tsx index e1e2b27f..45898aef 100644 --- a/frontend/src/providers/flow-provider.tsx +++ b/frontend/src/providers/flow-provider.tsx @@ -177,12 +177,18 @@ export function FlowProvider({ children }: FlowProviderProps) { const flowStatus = useMemo(() => flowData?.flow?.status, [flowData?.flow?.status]); - // Show toast notification when flow loading error occurs + // Show toast notification when flow loading error occurs. + // A single Postgres "no rows in result set" surfaces here every time a sibling + // query/subscription retries against an invalid flow id; without a stable + // toast id Sonner would stack 8 copies of the same message before the page + // redirects. Surface a friendly message and drop the raw SQL detail entirely. useEffect(() => { if (flowError) { - const description = flowError.message || 'An error occurred while loading flow'; - toast.error('Failed to load flow', { - description, + const raw = flowError.message ?? ''; + const isNotFound = /no rows in result set|not found/i.test(raw); + toast.error(isNotFound ? 'Flow not found' : 'Failed to load flow', { + description: isNotFound ? undefined : raw || undefined, + id: 'flow-load-error', }); Log.error('Error loading flow:', flowError); }