From 0a902a0867b188ec7aeff2da95c426a480c5f7ba Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 15 Jan 2026 16:01:27 +0100 Subject: [PATCH 1/5] refactor(platform): use Tailwind v4 canonical !important syntax Convert legacy !important prefix syntax (!class) to canonical suffix syntax (class!) across ReactFlow components and add unstyled variant to Input component for cleaner style overrides. Co-Authored-By: Claude Opus 4.5 --- .../automations/components/add-step-node.tsx | 2 +- .../components/automation-loop-container.tsx | 27 +++++++------- .../components/automation-sidepanel.tsx | 4 +- .../components/automation-step.tsx | 27 +++++++------- .../components/automation-steps.tsx | 4 +- .../components/invisible-handle.tsx | 17 +++++++++ .../chat/components/chat-search-dialog.tsx | 3 +- services/platform/app/globals.css | 37 +++++++++++++++++++ .../platform/components/ui/forms/input.tsx | 13 +++++-- 9 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 services/platform/app/(app)/dashboard/[id]/automations/components/invisible-handle.tsx diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/add-step-node.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/add-step-node.tsx index e008accf0..649f607f9 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/add-step-node.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/add-step-node.tsx @@ -18,7 +18,7 @@ export function AddStepNode({ data }: AddStepNodeProps) { {/* Bottom Target Handle - incoming from lower-ranked nodes */} - {/* Bottom Source Handle - outgoing to lower-ranked nodes */} - diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx index f3a211150..6e1bbe7fa 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx @@ -130,8 +130,8 @@ export function AutomationSidePanel({ return (
{/* Resize handle - hidden on mobile */}
{/* Top Target Handle - incoming from higher-ranked nodes */} - {/* Top Source Handle - outgoing to higher-ranked nodes */} - {/* Left Target Handle - for backward connections coming from the side */} - {/* Right Source Handle - for backward connections going to the side */} - @@ -168,21 +169,21 @@ export function AutomationStep({ data }: AutomationStepProps) { {cardContent} {/* Bottom Target Handle - incoming from lower-ranked nodes */} - {/* Bottom Source Handle - outgoing to lower-ranked nodes */} - diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx index e6b345fd7..d58c76ccf 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx @@ -1127,7 +1127,7 @@ function AutomationStepsInner({ {/* Draft Banner */} {showDraftBanner && isDraft && ( - +

@@ -1146,7 +1146,7 @@ function AutomationStepsInner({ {/* Active Automation Banner */} {isActive && ( - +

diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/invisible-handle.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/invisible-handle.tsx new file mode 100644 index 000000000..e52bd32f9 --- /dev/null +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/invisible-handle.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { Handle, type HandleProps } from '@xyflow/react'; +import { cn } from '@/lib/utils/cn'; + +/** + * An invisible ReactFlow Handle that overrides the default handle styling. + * Used for connecting nodes without showing visible connection points. + */ +export function InvisibleHandle({ className, ...props }: HandleProps) { + return ( + + ); +} diff --git a/services/platform/app/(app)/dashboard/[id]/chat/components/chat-search-dialog.tsx b/services/platform/app/(app)/dashboard/[id]/chat/components/chat-search-dialog.tsx index e5cb97e04..e85be1837 100644 --- a/services/platform/app/(app)/dashboard/[id]/chat/components/chat-search-dialog.tsx +++ b/services/platform/app/(app)/dashboard/[id]/chat/components/chat-search-dialog.tsx @@ -113,7 +113,8 @@ export function ChatSearchDialog({ value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={handleKeyDown} - className="!outline-none !ring-0 !ring-offset-0 !border-0 pr-9 p-0 h-6" + variant="unstyled" + className="pr-9 p-0 h-6" /> - - - ))} - - - - - - + + + + + + {versions.map((version) => ( + handleVersionChange(version._id)} + > + {version.version} + + {version.status === 'draft' && tCommon('status.draft')} + {version.status === 'active' && tCommon('status.active')} + {version.status === 'archived' && t('navigation.archived')} + + + ))} + + )} - {automation?.status === 'draft' && ( - - )} + {/* Desktop: Show buttons directly */} +

+ {automation?.status === 'draft' && ( + + )} - {automation?.status === 'active' && ( - - )} + {automation?.status === 'active' && ( + + )} + + {automation?.status === 'archived' && ( + + )} +
- {automation?.status === 'archived' && ( - + {/* Mobile: Show options dropdown */} + {(automation?.status === 'draft' || + automation?.status === 'active' || + automation?.status === 'archived') && ( + + + + + + {automation?.status === 'draft' && ( + + + {isPublishing + ? t('navigation.publishing') + : t('navigation.publish')} + + )} + {automation?.status === 'active' && ( + + + {tCommon('actions.edit')} + + )} + {automation?.status === 'archived' && ( + + + {isPublishing + ? t('navigation.rollingBack') + : t('navigation.rollback')} + + )} + + )}
diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx index 6e1bbe7fa..2bc3c8186 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-sidepanel.tsx @@ -145,11 +145,11 @@ export function AutomationSidePanel({
{/* Panel header */} -
+
{showAIChat ? ( <> -
+
diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx index d58c76ccf..888ac9487 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx @@ -1127,15 +1127,16 @@ function AutomationStepsInner({ {/* Draft Banner */} {showDraftBanner && isDraft && ( - -
- + +
+

{t('steps.banners.draftNotPublished')}

diff --git a/services/platform/app/(app)/dashboard/[id]/automations/layout.tsx b/services/platform/app/(app)/dashboard/[id]/automations/layout.tsx index 5d80e8eec..87b7d8d8f 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/layout.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/layout.tsx @@ -2,11 +2,12 @@ import { Input } from '@/components/ui/forms/input'; import { Badge } from '@/components/ui/feedback/badge'; +import { Button } from '@/components/ui/primitives/button'; import { api } from '@/convex/_generated/api'; import type { Id } from '@/convex/_generated/dataModel'; import { cn } from '@/lib/utils/cn'; import { useQuery } from 'convex/react'; -import { useParams, useRouter } from 'next/navigation'; +import { useParams, usePathname, useRouter } from 'next/navigation'; import { useUpdateAutomation } from './hooks/use-update-automation'; import { ReactNode, useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; @@ -16,6 +17,13 @@ import { useAuth } from '@/hooks/use-convex-auth'; import { AdaptiveHeaderRoot } from '@/components/layout/adaptive-header'; import { StickyHeader } from '@/components/layout/sticky-header'; import { useT } from '@/lib/i18n/client'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/overlays/dropdown-menu'; +import { ChevronDown } from 'lucide-react'; interface AutomationsLayoutProps { children: ReactNode; @@ -47,6 +55,24 @@ export default function AutomationsLayout({ organizationId: params.id as string, }); + // Fetch all versions of this automation for mobile version select + const versions = useQuery( + api.wf_definitions.listVersionsPublic, + automation?.name && params.id + ? { + organizationId: params.id as string, + name: automation.name, + } + : 'skip', + ); + + const pathname = usePathname(); + + const handleVersionChange = (versionId: string) => { + const currentPath = pathname.split('/automations/')[0]; + router.push(`${currentPath}/automations/${versionId}?panel=ai-chat`); + }; + // Check user authorization const userRole = (userContext?.role ?? '').toLowerCase(); const isAuthorized = userRole === 'admin' || userRole === 'developer'; @@ -92,78 +118,107 @@ export default function AutomationsLayout({ return ( <> - - -

- {/* "Automations /" prefix - hidden on mobile */} + +

+ {/* "Automations /" prefix - hidden on mobile */} + + {t('title')}   + + {automation?.name && !editMode && ( - {t('title')}   - - {automation?.name && !editMode && ( - setEditMode(true)} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - setEditMode(true); - } - }} - > - {/* "/" separator - hidden on mobile */} - /   - {automation?.name} - - )} -

- {editMode && ( - setEditMode(true)} onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSubmitAutomationName(); - } - if (e.key === 'Escape') { - setEditMode(false); + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + setEditMode(true); } }} - /> - )} - {automation?.status === 'draft' && ( - - {tCommon('status.draft')} - - )} - {automation?.status === 'active' && ( - - {tCommon('status.active')} - - )} - {automation?.status === 'archived' && ( - - {tCommon('status.archived')} - + > + {/* "/" separator - hidden on mobile */} + /   + {automation?.name} + )} -
- - +

+ {editMode && ( + { + if (e.key === 'Enter') { + handleSubmitAutomationName(); + } + if (e.key === 'Escape') { + setEditMode(false); + } + }} + /> + )} + {automation?.status === 'draft' && ( + + {tCommon('status.draft')} + + )} + {automation?.status === 'active' && ( + + {tCommon('status.active')} + + )} + {automation?.status === 'archived' && ( + + {tCommon('status.archived')} + + )} + + {/* Mobile-only version select */} + {automation && versions && versions.length > 0 && ( + + + + + + {versions.map((version) => ( + handleVersionChange(version._id)} + > + {version.version} + + {version.status === 'draft' && tCommon('status.draft')} + {version.status === 'active' && tCommon('status.active')} + {version.status === 'archived' && t('navigation.archived')} + + + ))} + + + )} +
+ {children} diff --git a/services/platform/types/xyflow.d.ts b/services/platform/types/xyflow.d.ts new file mode 100644 index 000000000..ebdd3ab12 --- /dev/null +++ b/services/platform/types/xyflow.d.ts @@ -0,0 +1,5 @@ +// Type declarations for XYFlow/ReactFlow CSS imports +declare module '@xyflow/react/dist/style.css' { + const content: string; + export default content; +} From 6979755bbedbc82a05a6a3567df1142e425d3ef7 Mon Sep 17 00:00:00 2001 From: israel Date: Thu, 15 Jan 2026 18:01:31 +0100 Subject: [PATCH 3/5] chore(platform): formatting and minor UI refinements - Format automation-loop-container and automation-step aria-label - Update adaptive-header z-index and use shrink-0 - Format input.tsx trailing commas - Update tab-navigation min-height and formatting - Add viewAutomation translation key - Update generated Convex files and package-lock Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 12 ------------ .../components/automation-loop-container.tsx | 6 +++++- .../automations/components/automation-step.tsx | 6 +++++- .../platform/app/(app)/dashboard/[id]/layout.tsx | 2 +- .../platform/components/layout/adaptive-header.tsx | 4 ++-- services/platform/components/ui/forms/input.tsx | 14 ++++++++++---- .../components/ui/navigation/tab-navigation.tsx | 11 ++++++----- services/platform/convex/_generated/api.d.ts | 2 ++ .../convex/betterAuth/_generated/component.ts | 2 ++ services/platform/messages/en.json | 3 ++- 10 files changed, 35 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6cea7d9bf..55feaa14d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10538,17 +10538,6 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz", - "integrity": "sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==", - "deprecated": "This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.", - "dev": true, - "license": "MIT", - "dependencies": { - "uuid": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.53.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", @@ -23482,7 +23471,6 @@ "@types/react-dom": "19.2.3", "@types/react-file-icon": "1.0.4", "@types/striptags": "3.1.1", - "@types/uuid": "11.0.0", "@vitejs/plugin-react": "5.1.2", "convex-test": "0.0.40", "encoding": "0.1.13", diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-loop-container.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-loop-container.tsx index 5aaab7cfc..6298050cd 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-loop-container.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-loop-container.tsx @@ -75,7 +75,11 @@ export function AutomationLoopContainer({ {/* Main Card - matches regular automation step styling */}