From 7eb9cc4c9c745871f34ee9af07d12ccb4ba183f7 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Tue, 22 Sep 2026 12:51:50 -0700 Subject: [PATCH 1/8] improvement(emcn): share larger chip and field geometry --- .claude/rules/emcn-components.md | 4 + .cursor/rules/emcn-components.mdc | 4 + apps/sim/app/(auth)/components/auth-input.tsx | 17 +--- .../landing-cta-link/landing-cta-link.tsx | 2 +- .../secret-value-field/secret-value-field.tsx | 2 +- .../components/chip-input/chip-input.test.tsx | 17 +++- .../src/components/chip-input/chip-input.tsx | 15 ++- .../emcn/src/components/chip/chip-chrome.ts | 6 +- .../emcn/src/components/chip/chip.test.tsx | 93 +++++++++++++++++++ packages/emcn/src/components/chip/chip.tsx | 36 +++++-- 10 files changed, 169 insertions(+), 27 deletions(-) create mode 100644 packages/emcn/src/components/chip/chip.test.tsx diff --git a/.claude/rules/emcn-components.md b/.claude/rules/emcn-components.md index af477a7d886..8c8b468d246 100644 --- a/.claude/rules/emcn-components.md +++ b/.claude/rules/emcn-components.md @@ -70,3 +70,7 @@ geometry only; colour, radius and SVG stroke continue to come from the selected Use `shape='round'` for circular actions, or omit it to retain the current radius. `size='inline'` is a 20px-high action with caption typography and compact horizontal padding. Prefer these supported props to size, padding and radius overrides. + +### Chip sizing and centered actions + +`Chip`, `ChipLink`, and `ChipInput` retain their default 30px height. Use `size="lg"` for the existing auth-scale 36px controls; the larger size follows the root font size. `Chip` and `ChipLink` accept `align="center"` to center their icon/label group, including full-width form actions. Long labels still shrink and retain their overflow treatment. Avoid descendant flex overrides to center chip labels. The default geometry exports remain unchanged for static consumers. diff --git a/.cursor/rules/emcn-components.mdc b/.cursor/rules/emcn-components.mdc index 6a70e29d4b3..75fe49a83e3 100644 --- a/.cursor/rules/emcn-components.mdc +++ b/.cursor/rules/emcn-components.mdc @@ -71,3 +71,7 @@ geometry only; colour, radius and SVG stroke continue to come from the selected Use `shape='round'` for circular actions, or omit it to retain the current radius. `size='inline'` is a 20px-high action with caption typography and compact horizontal padding. Prefer these supported props to size, padding and radius overrides. + +### Chip sizing and centered actions + +`Chip`, `ChipLink`, and `ChipInput` retain their default 30px height. Use `size="lg"` for the existing auth-scale 36px controls; the larger size follows the root font size. `Chip` and `ChipLink` accept `align="center"` to center their icon/label group, including full-width form actions. Long labels still shrink and retain their overflow treatment. Avoid descendant flex overrides to center chip labels. The default geometry exports remain unchanged for static consumers. diff --git a/apps/sim/app/(auth)/components/auth-input.tsx b/apps/sim/app/(auth)/components/auth-input.tsx index 396bae64cf8..a2c3bc12979 100644 --- a/apps/sim/app/(auth)/components/auth-input.tsx +++ b/apps/sim/app/(auth)/components/auth-input.tsx @@ -1,20 +1,11 @@ 'use client' import * as React from 'react' -import { ChipInput, type ChipInputProps, cn } from '@sim/emcn' -import { AUTH_CONTROL_HEIGHT } from '@/app/(auth)/components/constants' +import { ChipInput, type ChipInputProps } from '@sim/emcn' -/** - * The auth text field — a {@link ChipInput} raised to the auth control height - * ({@link AUTH_CONTROL_HEIGHT}) so every labeled field on the auth and invite - * surfaces shares one slightly-taller geometry. All chip props pass through - * (`error`, `endAdornment`, `icon`, …); only the height is owned here, and a - * caller's `className` (layout only) still composes on top. - */ -export const AuthInput = React.forwardRef( - ({ className, ...props }, ref) => ( - - ) +/** Auth fields use the larger shared chip size while retaining native input props and refs. */ +export const AuthInput = React.forwardRef>( + (props, ref) => ) AuthInput.displayName = 'AuthInput' diff --git a/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx b/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx index 4fbba3d3750..5787f2c8560 100644 --- a/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx +++ b/apps/sim/app/(landing)/components/landing-cta-link/landing-cta-link.tsx @@ -9,7 +9,7 @@ type LandingCtaSize = 'compact' | 'default' | 'display' export type LandingCtaSection = PostHogEventMap['landing_cta_clicked']['section'] -interface LandingCtaLinkProps extends Omit { +interface LandingCtaLinkProps extends Omit { size?: LandingCtaSize variant?: 'primary' | 'outline' /** Adds the animated chevron used by demo actions. */ diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx index a843694bfe8..fa0e488c761 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field/secret-value-field.tsx @@ -11,7 +11,7 @@ const VIEWER_MASK_LENGTH = 10 type SecretValueFieldProps = Omit< ComponentProps<'input'>, - 'type' | 'value' | 'onChange' | 'readOnly' | 'style' + 'type' | 'value' | 'onChange' | 'readOnly' | 'style' | 'size' > & { /** The chip owns field styling; callers use className for layout. */ style?: never diff --git a/packages/emcn/src/components/chip-input/chip-input.test.tsx b/packages/emcn/src/components/chip-input/chip-input.test.tsx index acbc4226246..a1d70082ff6 100644 --- a/packages/emcn/src/components/chip-input/chip-input.test.tsx +++ b/packages/emcn/src/components/chip-input/chip-input.test.tsx @@ -30,6 +30,19 @@ afterEach(() => { }) describe('ChipInput', () => { + it.each([undefined, 'lg'] as const)( + 'emits a single height for size %s without forwarding it to the native field', + (size) => { + const input = mount() + const heights = input.parentElement?.className + .split(' ') + .filter((token) => token.startsWith('h-')) + expect(heights).toEqual([size === 'lg' ? 'h-9' : 'h-[30px]']) + expect(input.hasAttribute('size')).toBe(false) + expect(input.disabled).toBe(true) + } + ) + it('keeps the focused input mounted when custom leading content changes', () => { const input = mount() const render = (color: string) => ( @@ -76,7 +89,7 @@ describe('chip form controls', () => { error aria-invalid aria-describedby='error' - className='h-[34px]' + size='lg' />

Enter a work email

{ expect(input.labels?.[0].textContent).toBe('Work email') expect(input.getAttribute('aria-describedby')).toBe('error') expect(input.getAttribute('aria-invalid')).toBe('true') - expect(input.parentElement?.className).toContain('h-[34px]') + expect(input.parentElement?.className).toContain('h-9') expect(input.parentElement?.className).toContain('border-[var(--text-error)]') expect(textareaRef.current?.rows).toBe(3) expect(textareaRef.current?.className).toContain('min-h-[80px]') diff --git a/packages/emcn/src/components/chip-input/chip-input.tsx b/packages/emcn/src/components/chip-input/chip-input.tsx index 444cbb1704a..3c5d7d1f161 100644 --- a/packages/emcn/src/components/chip-input/chip-input.tsx +++ b/packages/emcn/src/components/chip-input/chip-input.tsx @@ -28,11 +28,19 @@ */ import * as React from 'react' import { cn } from '../../lib/cn' -import { chipFieldSurfaceClass, chipFieldTextClass, chipGeometryClass } from '../chip/chip-chrome' +import { + chipContentGeometryClass, + chipFieldSurfaceClass, + chipFieldTextClass, + chipRadiusClass, + chipSizeClasses, +} from '../chip/chip-chrome' type ChipInputIcon = React.ComponentType<{ className?: string }> export interface ChipInputProps extends Omit, 'size'> { + /** Control height: 30px by default, or the larger 36px auth spacing scale. */ + size?: keyof typeof chipSizeClasses /** Leading icon component (e.g. `Search` from `@sim/emcn/icons`). Rendered at 14px in `--text-icon`, with the chip's 1.5 gap. */ icon?: ChipInputIcon /** Custom leading content, such as a color swatch. Takes precedence over `icon`. */ @@ -62,6 +70,7 @@ export const ChipInput = React.forwardRef( error, disabled, type = 'text', + size = 'md', ...props }, ref @@ -69,7 +78,9 @@ export const ChipInput = React.forwardRef(
{ + it.each([undefined, 'lg'] as const)( + 'uses one height for raw variants, buttons and links at size %s', + (size) => { + const height = size === 'lg' ? 'h-9' : 'h-[30px]' + expect( + chipVariants({ size }) + .split(' ') + .filter((token) => token.startsWith('h-')) + ).toEqual([height]) + for (const node of [ + + Continue + , + + Continue + , + ]) { + const markup = renderToStaticMarkup(node) + expect(markup).toContain(height) + expect(markup).not.toContain(size === 'lg' ? 'h-[30px]' : 'h-9') + expect(markup).not.toMatch(/ size=/) + } + expect(chipGeometryUnroundedClass).toContain('h-[30px]') + expect(chipGeometryClass).toContain('h-[30px]') + expect(chipGeometryClass).toContain('rounded-lg') + } + ) + + it('centers the icon and label without preventing long text from shrinking', () => { + const markup = renderToStaticMarkup( + }> + Continue with your identity provider + + ) + expect(markup).toContain('justify-center') + expect(markup).toContain('flex-initial') + expect(markup).toContain('min-w-0') + expect(markup).not.toContain('flex-none') + expect(markup).not.toMatch(/ align=/) + }) + + it('preserves native refs, focus, submission, disabled actions and link navigation', () => { + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + const button = createRef() + const link = createRef() + let submissions = 0 + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + const render = (disabled: boolean) => + act(() => + root.render( +
{ + event.preventDefault() + submissions++ + }} + > + + Continue + + + Workspace + +
+ ) + ) + try { + render(false) + button.current?.focus() + expect(document.activeElement).toBe(button.current) + act(() => button.current?.click()) + expect(submissions).toBe(1) + expect(link.current?.getAttribute('href')).toBe('/workspace') + render(true) + act(() => button.current?.click()) + expect(submissions).toBe(1) + expect(button.current?.disabled).toBe(true) + } finally { + act(() => root.unmount()) + container.remove() + } + }) +}) diff --git a/packages/emcn/src/components/chip/chip.tsx b/packages/emcn/src/components/chip/chip.tsx index 5b333032762..d8d3fbd2494 100644 --- a/packages/emcn/src/components/chip/chip.tsx +++ b/packages/emcn/src/components/chip/chip.tsx @@ -14,17 +14,18 @@ import { OverflowText, overflowTextClipClass } from '../overflow-text/overflow-t import { chipActiveSurfaceClass, chipBorderShadowRing, + chipContentGeometryClass, chipContentIconClass, chipContentLabelClass, chipFilledFillTokens, - chipGeometryUnroundedClass, chipHoverSurfaceClass, chipPrimaryFillTokens, chipRadiusClass, + chipSizeClasses, } from './chip-chrome' /** - * 30px pill — the platform's most common chrome pattern. + * 30px pill (36px with `size="lg"` at the default root font size) — the platform's most common chrome pattern. * * Render targets: * - {@link Chip} → ` -
- + name={tag.displayName} + typeLabel={FIELD_TYPE_LABELS[tag.fieldType] || tag.fieldType} + detail={formatValueForDisplay(tag.value, tag.fieldType)} + truncateDetail + removeLabel='Remove tag' + onRemove={(e) => { + e.stopPropagation() + handleRemoveTag(index) + }} + /> {editingTagIndex === index && (
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx index 46163ac91ea..8278f269f57 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx @@ -14,7 +14,6 @@ import { type ComboboxOption, handleKeyboardActivation, } from '@sim/emcn' -import { Trash } from '@sim/emcn/icons' import { createLogger } from '@sim/logger' import { getDocumentIcon } from '@/components/icons/document-icons' import type { TagUsageData } from '@/lib/api/contracts/knowledge' @@ -24,6 +23,7 @@ import { SUPPORTED_FIELD_TYPES, TAG_SLOT_CONFIG, } from '@/lib/knowledge/constants' +import { KnowledgeTagRow } from '@/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row' import { type TagDefinition, useKnowledgeBaseTagDefinitions, @@ -270,41 +270,28 @@ export function BaseTagsModal({ open, onOpenChange, knowledgeBaseId }: BaseTagsM {kbTagDefinitions.map((tag) => { const usage = getTagUsage(tag.tagSlot) return ( -
handleViewDocuments(tag)} onKeyDown={(event) => { if (event.target !== event.currentTarget) return handleKeyboardActivation(event, () => handleViewDocuments(tag)) }} - > - - {tag.displayName} - - - {FIELD_TYPE_LABELS[tag.fieldType] || tag.fieldType} - -
- - {usage.documentCount} document{usage.documentCount !== 1 ? 's' : ''} - -
- -
-
+ name={tag.displayName} + typeLabel={FIELD_TYPE_LABELS[tag.fieldType] || tag.fieldType} + detail={ + <> + {usage.documentCount} document{usage.documentCount !== 1 ? 's' : ''} + + } + removeLabel='Delete Tag' + onRemove={(e) => { + e.stopPropagation() + handleDeleteTagClick(tag) + }} + /> ) })} diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx new file mode 100644 index 00000000000..c94e9f37940 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx @@ -0,0 +1,55 @@ +import type { HTMLAttributes, MouseEventHandler, ReactNode } from 'react' +import { Button, cn } from '@sim/emcn' +import { Trash } from '@sim/emcn/icons' + +interface KnowledgeTagRowProps + extends Omit, 'children' | 'className'> { + name: string + typeLabel: string + detail: ReactNode + truncateDetail?: boolean + removeLabel: string + onRemove: MouseEventHandler +} + +/** Shared tag summary; callers retain activation, keyboard and removal behavior. */ +export function KnowledgeTagRow({ + name, + typeLabel, + detail, + truncateDetail = false, + removeLabel, + onRemove, + ...props +}: KnowledgeTagRowProps) { + return ( +
+ {name} + + {typeLabel} + +
+ + {detail} + +
+ +
+
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx index 5169f4697fd..373527a983b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx @@ -1020,12 +1020,12 @@ export function ConditionInput({
+ */ + 'ghost-destructive': 'text-[var(--text-error)] hover-hover:text-[var(--text-error)]', + /** + * Muted destructive action that turns red on hover. + * @example + */ + 'ghost-destructive-muted': 'text-[var(--text-muted)] hover-hover:text-[var(--text-error)]', subtle: 'text-[var(--text-body)] hover-hover:text-[var(--text-body)] hover-hover:bg-[var(--surface-4)]', 'ghost-secondary': 'text-[var(--text-muted)] hover-hover:text-[var(--text-primary)]', From 79cd1decb74abfc406cf37663c1e9c3cd9e199b1 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Tue, 22 Sep 2026 14:02:06 -0700 Subject: [PATCH 4/8] improvement(ui): use standard knowledge tag icon size --- .../[workspaceId]/knowledge/components/knowledge-tag-row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx index c94e9f37940..f416b7c4292 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/components/knowledge-tag-row.tsx @@ -45,7 +45,7 @@ export function KnowledgeTagRow({ aria-label={removeLabel} variant='ghost-destructive-muted' onClick={onRemove} - className='size-4 p-0' + size='icon' > From 6f0169c9dc596be6c8647c7ef51562418b25eb1c Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Tue, 22 Sep 2026 14:02:38 -0700 Subject: [PATCH 5/8] refactor(ui): consolidate product corner radii --- apps/sim/app/(auth)/login/loading.tsx | 20 ++++++------- .../sim/app/(auth)/reset-password/loading.tsx | 12 ++++---- apps/sim/app/(auth)/signup/loading.tsx | 24 ++++++++-------- apps/sim/app/(auth)/sso/loading.tsx | 12 ++++---- apps/sim/app/(auth)/verify/loading.tsx | 8 +++--- apps/sim/app/(auth)/verify/verify-content.tsx | 4 +-- .../chat/[identifier]/loading.tsx | 12 ++++---- .../chat/components/input/input.tsx | 2 +- .../loading-state/loading-state.tsx | 4 +-- .../message/components/markdown-renderer.tsx | 2 +- .../chat/components/message/message.tsx | 2 +- .../[workflowId]/[executionId]/loading.tsx | 26 ++++++++--------- .../[executionId]/resume-page-client.tsx | 24 ++++++++-------- apps/sim/app/_styles/globals.css | 3 ++ apps/sim/app/cli/auth/cli-auth-view.tsx | 2 +- apps/sim/app/cli/auth/loading.tsx | 10 +++---- apps/sim/app/invite/[id]/loading.tsx | 10 +++---- apps/sim/app/unsubscribe/loading.tsx | 10 +++---- .../components/folders/use-row-drag-ghost.ts | 2 +- .../rich-markdown-editor/image.tsx | 2 +- .../menus/suggestion-menu-chrome.ts | 2 +- .../rich-markdown-editor.css | 28 +++++++++---------- .../agent-group/browser-agent-icon.tsx | 2 +- .../components/chat-content/chat-content.tsx | 4 +-- .../components/chat-content/external-link.tsx | 2 +- .../message-sources/message-sources.tsx | 2 +- .../components/resource-mention.tsx | 2 +- .../components/source-card/source-card.tsx | 2 +- .../components/source-chip/source-chip.tsx | 2 +- .../mothership-chat-skeleton.tsx | 18 ++++++------ .../mothership-chat/mothership-chat.tsx | 4 +-- .../browser-session/browser-page-issue.tsx | 2 +- .../browser-session/browser-session.tsx | 2 +- .../resource-content/resource-content.tsx | 2 +- .../resource-registry/browser-tab-icon.tsx | 2 +- .../queued-messages/queued-messages.tsx | 4 +-- .../user-message-content.tsx | 2 +- .../components/knowledge-tag-row.tsx | 2 +- .../components/status-bar/status-bar.tsx | 2 +- .../workflows-list/workflows-list.tsx | 2 +- .../logs/components/dashboard/dashboard.tsx | 6 ++-- .../components/log-details/log-details.tsx | 2 +- .../app/workspace/[workspaceId]/logs/logs.tsx | 4 +-- .../sandboxes/components/sandbox-editor.tsx | 2 +- .../no-organization-view.tsx | 2 +- .../table-grid/cells/cell-render.tsx | 2 +- .../table-grid/headers/column-header-menu.tsx | 2 +- .../headers/workflow-group-meta-cell.tsx | 2 +- .../components/chat-message/chat-message.tsx | 2 +- .../diff-controls/diff-controls.tsx | 2 +- .../condition-input/condition-input.tsx | 4 +-- .../components/eval-input/eval-input.tsx | 2 +- .../messages-input/messages-input.tsx | 2 +- .../model-fallback-list.tsx | 4 +-- .../selector-input/selector-input.tsx | 2 +- .../components/skill-input/skill-input.tsx | 2 +- .../sub-block/components/text/text.tsx | 2 +- .../components/tool-input/tool-input.tsx | 4 +-- .../toolbar/components/drag-preview.ts | 4 +-- .../components/output-panel/output-panel.tsx | 2 +- .../preview-editor/preview-editor.tsx | 6 ++-- .../components/block/block.tsx | 5 ++-- .../sidebar-footer/sidebar-footer.tsx | 2 +- .../w/components/sidebar/utils.ts | 2 +- apps/sim/blocks/custom/custom-block-icon.tsx | 2 +- .../components/slack-managed-users-modal.tsx | 2 +- .../src/note/note-block-view.tsx | 6 ++-- .../workflow-block/workflow-block-view.tsx | 2 +- 68 files changed, 181 insertions(+), 179 deletions(-) diff --git a/apps/sim/app/(auth)/login/loading.tsx b/apps/sim/app/(auth)/login/loading.tsx index 74f7bece6c9..dc95e136385 100644 --- a/apps/sim/app/(auth)/login/loading.tsx +++ b/apps/sim/app/(auth)/login/loading.tsx @@ -3,22 +3,22 @@ import { Skeleton } from '@sim/emcn' export default function LoginLoading() { return (
- +
- - + +
- - + +
- - + +
- - + +
- +
) } diff --git a/apps/sim/app/(auth)/reset-password/loading.tsx b/apps/sim/app/(auth)/reset-password/loading.tsx index d1910ac0425..ec447a579db 100644 --- a/apps/sim/app/(auth)/reset-password/loading.tsx +++ b/apps/sim/app/(auth)/reset-password/loading.tsx @@ -3,14 +3,14 @@ import { Skeleton } from '@sim/emcn' export default function ResetPasswordLoading() { return (
- - + +
- - + +
- - + +
) } diff --git a/apps/sim/app/(auth)/signup/loading.tsx b/apps/sim/app/(auth)/signup/loading.tsx index c1190c8385e..d7fd260a032 100644 --- a/apps/sim/app/(auth)/signup/loading.tsx +++ b/apps/sim/app/(auth)/signup/loading.tsx @@ -3,26 +3,26 @@ import { Skeleton } from '@sim/emcn' export default function SignupLoading() { return (
- +
- - + +
- - + +
- - + +
- - + +
- - + +
- +
) } diff --git a/apps/sim/app/(auth)/sso/loading.tsx b/apps/sim/app/(auth)/sso/loading.tsx index 116e47c136b..6b6c3cf5e42 100644 --- a/apps/sim/app/(auth)/sso/loading.tsx +++ b/apps/sim/app/(auth)/sso/loading.tsx @@ -3,14 +3,14 @@ import { Skeleton } from '@sim/emcn' export default function SSOLoading() { return (
- - + +
- - + +
- - + +
) } diff --git a/apps/sim/app/(auth)/verify/loading.tsx b/apps/sim/app/(auth)/verify/loading.tsx index d884048905a..5227fc028b4 100644 --- a/apps/sim/app/(auth)/verify/loading.tsx +++ b/apps/sim/app/(auth)/verify/loading.tsx @@ -3,10 +3,10 @@ import { Skeleton } from '@sim/emcn' export default function VerifyLoading() { return (
- - - - + + + +
) } diff --git a/apps/sim/app/(auth)/verify/verify-content.tsx b/apps/sim/app/(auth)/verify/verify-content.tsx index 88d1c2e9d67..b13d3ce432d 100644 --- a/apps/sim/app/(auth)/verify/verify-content.tsx +++ b/apps/sim/app/(auth)/verify/verify-content.tsx @@ -149,8 +149,8 @@ function VerificationFormFallback() { return (
-
-
+
+
) diff --git a/apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx b/apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx index 405a06bc0e7..8393900a280 100644 --- a/apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx +++ b/apps/sim/app/(interfaces)/chat/[identifier]/loading.tsx @@ -8,10 +8,10 @@ export default function ChatLoading() {
- - + +
- +
@@ -24,16 +24,16 @@ export default function ChatLoading() {
- +
- +
- +
diff --git a/apps/sim/app/(interfaces)/chat/components/input/input.tsx b/apps/sim/app/(interfaces)/chat/components/input/input.tsx index 0554b38bed5..0555fe6411a 100644 --- a/apps/sim/app/(interfaces)/chat/components/input/input.tsx +++ b/apps/sim/app/(interfaces)/chat/components/input/input.tsx @@ -172,7 +172,7 @@ export const ChatInput: React.FC<{ {attachedFiles.map((file) => ( -
+
{file.dataUrl ? (
- +
- +
diff --git a/apps/sim/app/(interfaces)/chat/components/message/components/markdown-renderer.tsx b/apps/sim/app/(interfaces)/chat/components/message/components/markdown-renderer.tsx index dd6199e063e..616dfda617e 100644 --- a/apps/sim/app/(interfaces)/chat/components/message/components/markdown-renderer.tsx +++ b/apps/sim/app/(interfaces)/chat/components/message/components/markdown-renderer.tsx @@ -101,7 +101,7 @@ const COMPONENTS = { }, inlineCode: ({ children }: { children?: React.ReactNode }) => ( - + {children} ), diff --git a/apps/sim/app/(interfaces)/chat/components/message/message.tsx b/apps/sim/app/(interfaces)/chat/components/message/message.tsx index 9458ed16be7..95cb39bf938 100644 --- a/apps/sim/app/(interfaces)/chat/components/message/message.tsx +++ b/apps/sim/app/(interfaces)/chat/components/message/message.tsx @@ -192,7 +192,7 @@ export const ClientChatMessage = memo(function ClientChatMessage({ /> ) : ( <> -
+
{getFileIcon(attachment.type)}
diff --git a/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/loading.tsx b/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/loading.tsx index 37d6027a3cb..e725843d701 100644 --- a/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/loading.tsx +++ b/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/loading.tsx @@ -5,34 +5,34 @@ export default function ResumeLoading() {
- - + +
- + {Array.from({ length: 3 }).map((_, i) => ( - + ))}
-
- - +
+ +
- - + +
- - + +
- - + +
diff --git a/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx b/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx index bb09919dc63..15c8d2ee86a 100644 --- a/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx +++ b/apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client.tsx @@ -158,7 +158,7 @@ function renderStructuredValuePreview(value: unknown) { const { text: stringValue, truncated } = truncateForPreview(String(value)) return (
-
+
{truncated ? `${stringValue}…` : stringValue}
{truncated && ( @@ -800,7 +800,7 @@ export default function ResumeExecutionPage({ {/* Main Layout */}
{/* Pause Points List */} -
+
@@ -832,17 +832,17 @@ export default function ResumeExecutionPage({ {/* Detail Panel */}
{loadingDetail && !selectedDetail ? ( -
+
Loading…
) : !selectedContextId ? ( -
+
Select a pause point
) : !selectedDetail ? ( -
+
Could not load details @@ -850,7 +850,7 @@ export default function ResumeExecutionPage({ ) : (
{/* Status Header */} -
+

@@ -868,7 +868,7 @@ export default function ResumeExecutionPage({

{selectedDetail.pausePoint.automaticResumeWaitingReason && ( -
+

{selectedDetail.pausePoint.automaticResumeWaitingReason} @@ -881,7 +881,7 @@ export default function ResumeExecutionPage({ {/* Already resolved - show form fields with submitted values */} {selectedStatus === 'resumed' || selectedStatus === 'failed' ? ( -

+
@@ -932,7 +932,7 @@ export default function ResumeExecutionPage({ <> {/* Display Data */} {responseStructureRows.length > 0 ? ( -
+
@@ -958,7 +958,7 @@ export default function ResumeExecutionPage({
) : ( -
+
@@ -972,7 +972,7 @@ export default function ResumeExecutionPage({ {/* Resume Form */} {isHumanMode && hasInputFormat ? ( -
+
@@ -1001,7 +1001,7 @@ export default function ResumeExecutionPage({
) : ( -
+
diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css index 78b947eac66..2e265b5b08e 100644 --- a/apps/sim/app/_styles/globals.css +++ b/apps/sim/app/_styles/globals.css @@ -191,6 +191,9 @@ --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-3xl: 1.5rem; /** * These names intentionally match the app's own `:root` custom properties, so diff --git a/apps/sim/app/cli/auth/cli-auth-view.tsx b/apps/sim/app/cli/auth/cli-auth-view.tsx index f7865af95da..33469cc3306 100644 --- a/apps/sim/app/cli/auth/cli-auth-view.tsx +++ b/apps/sim/app/cli/auth/cli-auth-view.tsx @@ -94,7 +94,7 @@ export function CliAuthView() { description='Approve only if the code below matches the one in your terminal.' />
-
+
{/* `pl` offsets the trailing letter-space `tracking` adds after the last glyph, which would otherwise pull the code left of optical center. */} {request.pairing} diff --git a/apps/sim/app/cli/auth/loading.tsx b/apps/sim/app/cli/auth/loading.tsx index e6a96d20127..911ae79010e 100644 --- a/apps/sim/app/cli/auth/loading.tsx +++ b/apps/sim/app/cli/auth/loading.tsx @@ -11,11 +11,11 @@ import { AuthShell } from '@/app/(auth)/components' export function CliAuthLoading() { return (
- - - - - + + + + +
) } diff --git a/apps/sim/app/invite/[id]/loading.tsx b/apps/sim/app/invite/[id]/loading.tsx index b26abbbaf59..31e3d619994 100644 --- a/apps/sim/app/invite/[id]/loading.tsx +++ b/apps/sim/app/invite/[id]/loading.tsx @@ -3,11 +3,11 @@ import { Skeleton } from '@sim/emcn' export default function InviteLoading() { return (
- - - - - + + + + +
) } diff --git a/apps/sim/app/unsubscribe/loading.tsx b/apps/sim/app/unsubscribe/loading.tsx index 5f625c75bbd..e5a3aa10a8f 100644 --- a/apps/sim/app/unsubscribe/loading.tsx +++ b/apps/sim/app/unsubscribe/loading.tsx @@ -3,11 +3,11 @@ import { Skeleton } from '@sim/emcn' export default function UnsubscribeLoading() { return (
- - - - - + + + + +
) } diff --git a/apps/sim/app/workspace/[workspaceId]/components/folders/use-row-drag-ghost.ts b/apps/sim/app/workspace/[workspaceId]/components/folders/use-row-drag-ghost.ts index ee37ca45a8c..a7bc8d07fc3 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/folders/use-row-drag-ghost.ts +++ b/apps/sim/app/workspace/[workspaceId]/components/folders/use-row-drag-ghost.ts @@ -11,7 +11,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' * the label inherit the app font and match the row it was lifted from. */ const DRAG_GHOST_STYLE = - 'position:fixed;top:-500px;left:0;display:inline-flex;align-items:center;padding:4px 10px;background:var(--surface-active);border:1px solid var(--border);border-radius:8px;font-size:13px;color:var(--text-body);white-space:nowrap;pointer-events:none;box-shadow:var(--shadow-medium);z-index:var(--z-toast)' + 'position:fixed;top:-500px;left:0;display:inline-flex;align-items:center;padding:4px 10px;background:var(--surface-active);border:1px solid var(--border);border-radius:var(--radius-lg);font-size:13px;color:var(--text-body);white-space:nowrap;pointer-events:none;box-shadow:var(--shadow-medium);z-index:var(--z-toast)' const DRAG_GHOST_LABEL_STYLE = 'max-width:200px;overflow:hidden;text-overflow:ellipsis' diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.tsx index d6623e648d6..7f9ac01de9c 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.tsx @@ -286,7 +286,7 @@ export function ResizableImageView({ node, selected, editor, getPos }: ReactNode onPointerDown={startResize} className='absolute right-0 bottom-0 flex size-10 cursor-nwse-resize touch-none items-end justify-end p-1 sm:size-8' > - + )} diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/suggestion-menu-chrome.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/suggestion-menu-chrome.ts index 918934aa0e1..353c615db0d 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/suggestion-menu-chrome.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/menus/suggestion-menu-chrome.ts @@ -17,7 +17,7 @@ export const SUGGESTION_SCROLL_CLASS = 'max-h-[240px] scroll-py-1.5 overflow-y-a /** A selectable row: icon + label, 14px icon in `--text-icon`, truncating label. The `img` rules * size custom-block image icons (rendered as ``, so the `svg` rules never reach them). */ export const SUGGESTION_ITEM_CLASS = - 'relative flex w-full min-w-0 cursor-pointer select-none items-center gap-2 rounded-[5px] px-2 py-1.5 text-left text-[var(--text-body)] text-caption outline-hidden transition-colors [&>span]:min-w-0 [&>span]:truncate [&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:text-[var(--text-icon)] [&_img]:pointer-events-none [&_img]:size-[14px] [&_img]:shrink-0' + 'relative flex w-full min-w-0 cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-[var(--text-body)] text-caption outline-hidden transition-colors [&>span]:min-w-0 [&>span]:truncate [&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:text-[var(--text-icon)] [&_img]:pointer-events-none [&_img]:size-[14px] [&_img]:shrink-0' /** A group heading above a run of rows. */ export const SUGGESTION_GROUP_LABEL_CLASS = diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.css b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.css index 3b6cc3f2b85..5f02b74d874 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.css +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.css @@ -28,7 +28,7 @@ .rich-markdown-nodes img { max-width: 100%; height: auto; - border-radius: 8px; + border-radius: var(--radius-lg); border: var(--border-width) solid var(--border); } @@ -37,7 +37,7 @@ .rich-markdown-nodes .ProseMirror-selectednode { outline: 2px solid var(--brand-secondary); outline-offset: 2px; - border-radius: 4px; + border-radius: var(--radius-sm); } /* An image is its own framed element; ring the image itself so the indicator hugs the picture @@ -226,7 +226,7 @@ height: 16px; margin: 0; border: var(--border-width) solid var(--border-1); - border-radius: 3px; + border-radius: var(--radius-sm); background: transparent; cursor: pointer; } @@ -258,7 +258,7 @@ font-family: var(--font-martian-mono, ui-monospace, monospace); font-size: 0.875em; background: var(--surface-5); - border-radius: 4px; + border-radius: var(--radius-sm); padding: 0.125rem 0.375rem; } @@ -279,7 +279,7 @@ .rich-markdown-prose pre, .rich-markdown-prose .mermaid-diagram-frame { background: var(--surface-5); - border-radius: 8px; + border-radius: var(--radius-lg); padding: 1rem; overflow-x: auto; } @@ -341,12 +341,12 @@ } .rich-markdown-nodes .raw-markdown-block { - border-radius: 8px; + border-radius: var(--radius-lg); padding: 0.75rem 1rem; } .rich-markdown-nodes .raw-markdown-inline { - border-radius: 4px; + border-radius: var(--radius-sm); padding: 0.0625rem 0.3rem; } @@ -373,7 +373,7 @@ .rich-markdown-nodes img.rich-leaf-in-selection { outline: 2px solid var(--selection-bg); outline-offset: 2px; - border-radius: 4px; + border-radius: var(--radius-sm); } /* Borders, padding, typography, and header fill come from document-table.css — the chrome shared @@ -442,7 +442,7 @@ .rich-markdown-nodes mark { background-color: color-mix(in srgb, var(--color-amber-400) 40%, transparent); color: inherit; - border-radius: 2px; + border-radius: var(--radius-xs); padding: 0 0.1em; margin: 0 -0.1em; box-decoration-break: clone; @@ -513,7 +513,7 @@ left: -1px; width: 8px; height: 5px; - border-radius: 2px 2px 2px 0; + border-radius: var(--radius-xs) var(--radius-xs) var(--radius-xs) 0; background-color: var(--caret-color); transition: opacity 0.2s ease; } @@ -551,7 +551,7 @@ max-width: 10rem; overflow: hidden; padding: 0.1rem 0.35rem; - border-radius: 2px 2px 2px 0; + border-radius: var(--radius-xs) var(--radius-xs) var(--radius-xs) 0; /* 11px = the `text-xs` the canvas/tables presence tags use, for pixel parity. */ font-size: 11px; font-weight: 500; @@ -576,13 +576,13 @@ .rich-markdown-nodes .collaboration-carets__caret--flip .collaboration-carets__label { left: auto; right: -1px; - border-radius: 2px 2px 0 2px; + border-radius: var(--radius-xs) var(--radius-xs) 0 var(--radius-xs); } /* Remote text selection: a rounded translucent tint of the collaborator's identity * color (the alpha fill is set inline by selectionRender). */ .rich-markdown-nodes .collaboration-carets__selection { - border-radius: 2px; + border-radius: var(--radius-xs); pointer-events: none; } @@ -591,7 +591,7 @@ .rich-markdown-nodes .rich-find-match { background-color: var(--highlight-match-bg); color: var(--highlight-match-text); - border-radius: 2px; + border-radius: var(--radius-xs); } /* The active hit is a stronger fill of the same hue, never a ring: an outline drawn diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/browser-agent-icon.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/browser-agent-icon.tsx index 5bf1c53ac4d..925e6451401 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/browser-agent-icon.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/browser-agent-icon.tsx @@ -127,7 +127,7 @@ function BrowserAgentFavicon({ url, canLoad }: BrowserAgentFaviconProps) { referrerPolicy='no-referrer' alt='' className={cn( - 'size-full rounded-[3px]', + 'size-full rounded-sm', status !== 'loaded' && 'pointer-events-none absolute opacity-0' )} onLoad={() => setStatus('loaded')} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx index 958b6b893bf..cdd5a769f06 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx @@ -334,7 +334,7 @@ const MARKDOWN_COMPONENTS = { className={cn( 'text-[var(--text-primary)]', kind - ? 'not-prose inline-flex items-baseline gap-1 rounded-[5px] bg-[var(--surface-5)] px-[5px] no-underline transition-colors hover-hover:bg-[var(--surface-6)]' + ? 'not-prose inline-flex items-baseline gap-1 rounded-sm bg-[var(--surface-5)] px-[5px] no-underline transition-colors hover-hover:bg-[var(--surface-6)]' : 'underline decoration-dashed underline-offset-4' )} onClick={(e) => { @@ -415,7 +415,7 @@ const MARKDOWN_COMPONENTS = { }, inlineCode({ children }: { children?: React.ReactNode }) { return ( - + {children} ) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/external-link.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/external-link.tsx index fd9661d2151..11829e5bef2 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/external-link.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/external-link.tsx @@ -71,7 +71,7 @@ export function ExternalLink({ href, hostname, children }: ExternalLinkProps) { diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/message-sources/message-sources.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/message-sources/message-sources.tsx index 305a02b97fe..1f1d029d321 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/message-sources/message-sources.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/message-sources/message-sources.tsx @@ -7,7 +7,7 @@ import type { SourceTagData } from '@/app/workspace/[workspaceId]/home/component /** The action-row button, matching the copy and vote buttons beside it with room for a count. */ const BUTTON_CLASSES = - 'flex h-[26px] items-center gap-1 rounded-[6px] px-1.5 text-[var(--text-icon)] text-caption transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-hidden data-[state=open]:bg-[var(--surface-active)] data-[state=open]:hover-hover:bg-[var(--surface-active)]' + 'flex h-[26px] items-center gap-1 rounded-md px-1.5 text-[var(--text-icon)] text-caption transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-hidden data-[state=open]:bg-[var(--surface-active)] data-[state=open]:hover-hover:bg-[var(--surface-active)]' interface MessageSourcesProps { sources: readonly SourceTagData[] diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/resource-mention.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/resource-mention.tsx index a6f685cd35f..0e72032d065 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/resource-mention.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/resource-mention.tsx @@ -9,7 +9,7 @@ interface ResourceMentionProps { export function ResourceMention({ icon, title, onSelect }: ResourceMentionProps) { const classes = - 'inline-flex items-baseline gap-1 rounded-[5px] bg-[var(--surface-5)] px-[5px] align-baseline font-[inherit] text-[inherit] leading-[inherit]' + 'inline-flex items-baseline gap-1 rounded-sm bg-[var(--surface-5)] px-[5px] align-baseline font-[inherit] text-[inherit] leading-[inherit]' const content = ( <> {icon} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-card/source-card.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-card/source-card.tsx index b339c1a91fc..3729be3d831 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-card/source-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-card/source-card.tsx @@ -145,7 +145,7 @@ export function SourceCard({ source, query, onSummarize, dense = false }: Source ) : null diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-chip.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-chip.tsx index 8d4540d4b9e..31332c215dd 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-chip.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/source-chip/source-chip.tsx @@ -75,7 +75,7 @@ export function SourceChip({ source }: SourceChipProps) { ) : null} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/components/mothership-chat-skeleton/mothership-chat-skeleton.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/components/mothership-chat-skeleton/mothership-chat-skeleton.tsx index 9b32c21b1dd..8c4714a5c1e 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/components/mothership-chat-skeleton/mothership-chat-skeleton.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/components/mothership-chat-skeleton/mothership-chat-skeleton.tsx @@ -27,24 +27,24 @@ export function MothershipChatSkeleton({ return (
- +
- - - - + + + +
- +
- - - + + +
) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx index 07254879b4f..e57b3c8f0f2 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx @@ -142,7 +142,7 @@ const LAYOUT_STYLES = { rowGap: 'pb-6', userRow: 'flex flex-col items-end gap-[6px] pt-3', attachmentWidth: 'max-w-[70%]', - userBubble: 'max-w-[70%] overflow-hidden rounded-[16px] bg-[var(--surface-5)] px-3.5 py-2', + userBubble: 'max-w-[70%] overflow-hidden rounded-2xl bg-[var(--surface-5)] px-3.5 py-2', assistantRow: 'group/msg', footer: 'shrink-0 px-[24px] pb-[16px]', footerInner: 'mx-auto max-w-chat', @@ -154,7 +154,7 @@ const LAYOUT_STYLES = { rowGap: 'pb-4', userRow: 'flex flex-col items-end gap-[6px] pt-2', attachmentWidth: 'max-w-[85%]', - userBubble: 'max-w-[85%] overflow-hidden rounded-[16px] bg-[var(--surface-5)] px-3 py-2', + userBubble: 'max-w-[85%] overflow-hidden rounded-2xl bg-[var(--surface-5)] px-3 py-2', assistantRow: 'group/msg', footer: 'shrink-0 px-3 pb-3', footerInner: '', diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-issue.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-issue.tsx index 199aa2b8be9..6c8299d27a0 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-issue.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-page-issue.tsx @@ -148,7 +148,7 @@ export function BrowserPageIssueView({ issue, onReload, focusRecovery }: Browser

{copy.headline} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.tsx index f3aa734a126..da9baa8aa3d 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session.tsx @@ -117,7 +117,7 @@ function BrowserSuggestionIcon({ suggestion }: { suggestion: UrlSuggestion }) { setFailed(true)} /> ) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index 5b6560b4eae..ed7e839a13d 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -804,7 +804,7 @@ function EmbeddedFolder({ workspaceId, folderId }: EmbeddedFolderProps) { key={w.id} type='button' onClick={() => openInternalLink(`/workspace/${workspaceId}/w/${w.id}`)} - className='flex items-center gap-2 rounded-[6px] px-3 py-2 text-left transition-colors hover:bg-[var(--surface-4)]' + className='flex items-center gap-2 rounded-md px-3 py-2 text-left transition-colors hover:bg-[var(--surface-4)]' > diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/browser-tab-icon.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/browser-tab-icon.tsx index cb0d6042c20..432ea7f74e9 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/browser-tab-icon.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry/browser-tab-icon.tsx @@ -64,7 +64,7 @@ export function BrowserTabIcon({ tabId, scopeId, className }: BrowserTabIconProp src={faviconUrl(hostname, 32)} alt='' className={cn( - 'size-[16px] rounded-[3px]', + 'size-[16px] rounded-sm', !faviconLoaded && 'pointer-events-none absolute opacity-0' )} onLoad={() => setFavicon({ hostname, status: 'loaded' })} diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/queued-messages/queued-messages.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/queued-messages/queued-messages.tsx index 466afe3e7de..57a8c63949b 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/queued-messages/queued-messages.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/queued-messages/queued-messages.tsx @@ -49,7 +49,7 @@ export function QueuedMessages({ return (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx index 373527a983b..67aeef659ff 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx @@ -946,10 +946,10 @@ export function ConditionInput({ className={cn( 'flex items-center justify-between overflow-hidden bg-transparent px-2.5 py-[5px]', isRouterMode - ? 'rounded-t-[4px] border-[var(--border-1)] border-b' + ? 'rounded-t-sm border-[var(--border-1)] border-b' : isElseConditionTitle(block.title) ? 'rounded-sm border-0' - : 'rounded-t-[4px] border-[var(--border-1)] border-b' + : 'rounded-t-sm border-[var(--border-1)] border-b' )} > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx index 652f79b6e3a..703028a1b40 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/eval-input/eval-input.tsx @@ -138,7 +138,7 @@ export function EvalInput({ } const renderMetricHeader = (metric: EvalMetric, index: number) => ( -
+
Metric {index + 1}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx index 3dcdd9347b8..501f6aab1ec 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/messages-input/messages-input.tsx @@ -614,7 +614,7 @@ export function MessagesInput({ type='button' disabled={isPreview || disabled} className={cn( - 'group -ml-1.5 -my-1 flex items-center gap-1 rounded px-1.5 py-1 text-[var(--text-primary)] text-small leading-none transition-colors hover-hover:bg-[var(--surface-5)] hover-hover:text-[var(--text-secondary)]', + 'group -ml-1.5 -my-1 flex items-center gap-1 rounded-sm px-1.5 py-1 text-[var(--text-primary)] text-small leading-none transition-colors hover-hover:bg-[var(--surface-5)] hover-hover:text-[var(--text-secondary)]', (isPreview || disabled) && 'cursor-default hover-hover:bg-transparent hover-hover:text-[var(--text-primary)]' )} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/model-fallback-list/model-fallback-list.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/model-fallback-list/model-fallback-list.tsx index de1caa66d55..3c527c360e9 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/model-fallback-list/model-fallback-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/model-fallback-list/model-fallback-list.tsx @@ -124,7 +124,7 @@ const FallbackRow = memo(function FallbackRow({ return (
-
+
{ordinalChoiceLabel(index)}
{canMove && ( @@ -179,7 +179,7 @@ const FallbackRow = memo(function FallbackRow({
-
+
-
+
Selector not supported for service: {serviceId}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx index dea669b0d4e..1becdfeeafd 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx @@ -161,7 +161,7 @@ export function SkillInput({ className='group relative flex flex-col overflow-hidden rounded-sm border border-[var(--border-1)] transition-all duration-200 ease-in-out' >
{ if (fullSkill && !disabled && !isPreview) { setEditingSkillId(fullSkill.id) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/text/text.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/text/text.tsx index f9327663ac3..8bebd8bed5b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/text/text.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/text/text.tsx @@ -33,7 +33,7 @@ export function Text({ blockId, subBlockId, content, className }: TextProps) { className={`rounded-md border bg-[var(--surface-2)] p-4 shadow-xs ${className || ''}`} >
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index 54b7d751123..1044d2392d5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -1635,7 +1635,7 @@ export const ToolInput = memo(function ToolInput({ >
{isExpandedForDisplay && ( -
+
{showToolControl && ( <> e.stopPropagation()} data-toolbar-root data-search-active='true' diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx index c7c6880bf90..468f622020a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/preview-editor.tsx @@ -1064,7 +1064,7 @@ function PreviewEditorContent({ return (
{/* Header - styled like subflow header */} -
+
{onClose && ( @@ -1096,7 +1096,7 @@ function PreviewEditorContent({ if (!blockConfig) { return (
-
+
{block.name || 'Unknown Block'} @@ -1154,7 +1154,7 @@ function PreviewEditorContent({ return (
{/* Header - styled like editor */} -
+
{block.type !== 'note' && } - + {desktopUpdateActionLabel(updateState)} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts index 9f358b9a265..613f4cb8f4c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/utils.ts @@ -51,7 +51,7 @@ export function createSidebarDragGhost(label: string, icon?: SidebarDragGhostIco padding: 4px 10px; background: var(--surface-active); border: 1px solid rgba(255,255,255,0.08); - border-radius: 8px; + border-radius: var(--radius-lg); font-family: system-ui, -apple-system, sans-serif; font-size: 13px; color: var(--text-body); diff --git a/apps/sim/blocks/custom/custom-block-icon.tsx b/apps/sim/blocks/custom/custom-block-icon.tsx index e767dbadf76..c482727e8f8 100644 --- a/apps/sim/blocks/custom/custom-block-icon.tsx +++ b/apps/sim/blocks/custom/custom-block-icon.tsx @@ -34,7 +34,7 @@ export function makeImageIcon(url: string): BlockIcon { src={url} alt='' style={style} - className={cn('size-full rounded-[4px] object-contain', className)} + className={cn('size-full rounded-sm object-contain', className)} /> )) // double-cast-allowed: an renderer must satisfy the SVG-typed BlockIcon slot diff --git a/apps/sim/ee/credential-groups/components/slack-managed-users-modal.tsx b/apps/sim/ee/credential-groups/components/slack-managed-users-modal.tsx index aee93dc4b36..99d65bf3993 100644 --- a/apps/sim/ee/credential-groups/components/slack-managed-users-modal.tsx +++ b/apps/sim/ee/credential-groups/components/slack-managed-users-modal.tsx @@ -436,7 +436,7 @@ export function SlackManagedUsersModal({ ) ) : isLoading ? (
- +
) : noBots ? ( diff --git a/packages/workflow-renderer/src/note/note-block-view.tsx b/packages/workflow-renderer/src/note/note-block-view.tsx index 6b71def6fb5..b62759366c8 100644 --- a/packages/workflow-renderer/src/note/note-block-view.tsx +++ b/packages/workflow-renderer/src/note/note-block-view.tsx @@ -108,7 +108,7 @@ const NOTE_REMARK_PLUGINS = [remarkGfm, remarkBreaks] */ const NOTE_TASK_CHECKBOX_CLASS = [ 'mt-[3px] inline-grid size-[16px] shrink-0 appearance-none place-content-center', - 'rounded-[3px] border border-[var(--border-1)] bg-transparent', + 'rounded-sm border border-[var(--border-1)] bg-transparent', 'checked:border-[var(--text-primary)] checked:bg-[var(--text-primary)]', "checked:after:size-[10px] checked:after:bg-[var(--surface-2)] checked:after:content-['']", 'checked:after:[clip-path:polygon(14%_44%,0_65%,50%_100%,100%_16%,80%_0%,43%_62%)]', @@ -251,14 +251,14 @@ const NOTE_COMPONENTS = { /> ), inlineCode: ({ children }: { children?: ReactNode }) => ( - + {children} ), code: ({ children, className, ...props }: { children?: ReactNode; className?: string }) => ( {children} diff --git a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx index cae87070f2f..f9a6414258b 100644 --- a/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx +++ b/packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx @@ -957,7 +957,7 @@ export function WorkflowBlockView({ )} {showErrorRow && (
event.stopPropagation()} From a820bbb3a1ef3325cef1e81cd4ad43852a235f9a Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos <157128530+BillLeoutsakosvl346@users.noreply.github.com> Date: Tue, 22 Sep 2026 19:13:33 -0700 Subject: [PATCH 6/8] improvement(ui): reuse chip for PII configuration action (#8173) Co-authored-by: Bill Leoutsakos --- .../grouped-checkbox-list.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx index d385a77ab71..31acd122b8a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/grouped-checkbox-list/grouped-checkbox-list.tsx @@ -4,11 +4,11 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Button, Checkbox, + Chip, ChipModal, ChipModalBody, ChipModalField, ChipModalHeader, - cn, } from '@sim/emcn' import { ManageWorkspace } from '@sim/emcn/icons' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' @@ -113,25 +113,22 @@ export function GroupedCheckboxList({ return ( <> - + Configure PII Types + setOpen(false)}>Select PII Types to Detect e.stopPropagation()}> From eeb4ac52d7dacc919b8a7f0b251eb806959bea2b Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos <157128530+BillLeoutsakosvl346@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:08:54 -0700 Subject: [PATCH 7/8] refactor(ui): share collapsible workflow field cards (#8017) Co-authored-by: Bill Leoutsakos --- .../document-tag-entry/document-tag-entry.tsx | 105 ++++++++-------- .../components/filter-rule-row.tsx | 110 ++++++++--------- .../knowledge-tag-filters.tsx | 115 +++++++++--------- .../sort-builder/components/sort-rule-row.tsx | 112 ++++++++--------- .../collapsible-card.test.tsx | 94 ++++++++++++++ .../collapsible-card/collapsible-card.tsx | 47 +++++-- 6 files changed, 339 insertions(+), 244 deletions(-) create mode 100644 packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx index b5abcaf47a4..7023aa4f226 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/document-tag-entry/document-tag-entry.tsx @@ -4,12 +4,13 @@ import { useMemo, useRef } from 'react' import { Badge, Button, + CollapsibleCard, Combobox, type ComboboxOption, cn, - handleKeyboardActivation, Input, Label, + OverflowText, Trash, } from '@sim/emcn' import { Plus } from '@sim/emcn/icons' @@ -235,55 +236,46 @@ export function DocumentTagEntry({ } /** - * Renders the tag header with name, badge, and action buttons + * Renders the tag summary with its type badge * Shows tag name only when collapsed (as summary), generic label when expanded */ - const renderTagHeader = (tag: DocumentTag, index: number) => ( -
toggleCollapse(tag.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - handleKeyboardActivation(event, () => toggleCollapse(tag.id)) - }} - > -
- - {tag.collapsed ? tag.tagName || `Tag ${index + 1}` : `Tag ${index + 1}`} - - {tag.collapsed && tag.tagName && ( - - {FIELD_TYPE_LABELS[tag.fieldType] || 'Text'} - - )} -
-
e.stopPropagation()} + const renderTitle = (tag: DocumentTag, index: number) => ( + + - - -
-
+ {tag.collapsed ? tag.tagName || `Tag ${index + 1}` : `Tag ${index + 1}`} + + {tag.collapsed && tag.tagName && ( + + {FIELD_TYPE_LABELS[tag.fieldType] || 'Text'} + + )} + + ) + + const renderActions = (tag: DocumentTag) => ( + <> + + + ) /** @@ -385,7 +377,7 @@ export function DocumentTagEntry({ })) return ( -
+ <>
Value {renderValueInput(tag)}
-
+ ) } return (
{tags.map((tag, index) => ( -
toggleCollapse(tag.id)} > - {renderTagHeader(tag, index)} {!tag.collapsed && renderTagContent(tag)} -
+ ))}
) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/components/filter-rule-row.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/components/filter-rule-row.tsx index d41572a72d6..7e0e4375b3b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/components/filter-rule-row.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/filter-builder/components/filter-rule-row.tsx @@ -2,12 +2,13 @@ import { useRef } from 'react' import { Badge, Button, + CollapsibleCard, Combobox, type ComboboxOption, cn, - handleKeyboardActivation, Input, Label, + OverflowText, Trash, } from '@sim/emcn' import { Plus } from '@sim/emcn/icons' @@ -102,56 +103,46 @@ export function FilterRuleRow({ label, }) - const renderHeader = () => ( -
onToggleCollapse(rule.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - handleKeyboardActivation(event, () => onToggleCollapse(rule.id)) - }} - > -
- - {rule.collapsed && rule.column - ? formatDisplayText(getColumnLabel(rule.column), { - workflowSearchHighlight: getLabelHighlight('column', getColumnLabel(rule.column)), - }) - : `Condition ${index + 1}`} - - {rule.collapsed && rule.column && ( - - {formatDisplayText(getOperatorLabel(rule.operator), { - workflowSearchHighlight: getLabelHighlight( - 'operator', - getOperatorLabel(rule.operator) - ), - })} - - )} -
-
e.stopPropagation()} + const renderTitle = () => ( + + - - -
-
+ {rule.collapsed && rule.column + ? formatDisplayText(getColumnLabel(rule.column), { + workflowSearchHighlight: getLabelHighlight('column', getColumnLabel(rule.column)), + }) + : `Condition ${index + 1}`} + + {rule.collapsed && rule.column && ( + + {formatDisplayText(getOperatorLabel(rule.operator), { + workflowSearchHighlight: getLabelHighlight('operator', getOperatorLabel(rule.operator)), + })} + + )} + + ) + + const renderActions = () => ( + <> + + + ) const renderValueInput = () => ( @@ -209,7 +200,7 @@ export function FilterRuleRow({ ) const renderContent = () => ( -
+ <> {index > 0 && (
@@ -281,19 +272,20 @@ export function FilterRuleRow({ {renderValueInput()}
-
+ ) return ( -
onToggleCollapse(rule.id)} > - {renderHeader()} {!rule.collapsed && renderContent()} -
+ ) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx index b5ca52dd694..dc4e6b62c34 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/knowledge-tag-filters/knowledge-tag-filters.tsx @@ -4,12 +4,13 @@ import { useRef } from 'react' import { Badge, Button, + CollapsibleCard, Combobox, type ComboboxOption, cn, - handleKeyboardActivation, Input, Label, + OverflowText, Trash, } from '@sim/emcn' import { Plus } from '@sim/emcn/icons' @@ -228,57 +229,52 @@ export function KnowledgeTagFilters({ } /** - * Renders the filter header with name, badge, and action buttons + * Renders the filter summary with its type badge * Shows tag name only when collapsed (as summary), generic label when expanded */ - const renderFilterHeader = (filter: TagFilter, index: number) => ( -
toggleCollapse(filter.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - handleKeyboardActivation(event, () => toggleCollapse(filter.id)) - }} - > -
- - {filter.collapsed ? filter.tagName || `Filter ${index + 1}` : `Filter ${index + 1}`} - - {filter.collapsed && filter.tagName && ( - - {FIELD_TYPE_LABELS[filter.fieldType] || 'Text'} - - )} -
-
- - -
-
+ const renderTitle = (filter: TagFilter, index: number) => ( + + + {filter.collapsed ? filter.tagName || `Filter ${index + 1}` : `Filter ${index + 1}`} + + {filter.collapsed && filter.tagName && ( + + {FIELD_TYPE_LABELS[filter.fieldType] || 'Text'} + + )} + + ) + + const renderActions = (filter: TagFilter) => ( + <> + + + ) /** @@ -383,7 +379,7 @@ export function KnowledgeTagFilters({ const isBetween = filter.operator === 'between' return ( -
+ <>
-
+ ) } return (
{filters.map((filter, index) => ( -
toggleCollapse(filter.id)} > - {renderFilterHeader(filter, index)} {!filter.collapsed && renderFilterContent(filter)} -
+ ))}
) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/components/sort-rule-row.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/components/sort-rule-row.tsx index 329adbef3c6..d831a90a785 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/components/sort-rule-row.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sort-builder/components/sort-rule-row.tsx @@ -1,11 +1,11 @@ import { Badge, Button, + CollapsibleCard, Combobox, type ComboboxOption, - cn, - handleKeyboardActivation, Label, + OverflowText, Trash, } from '@sim/emcn' import { Plus } from '@sim/emcn/icons' @@ -61,60 +61,51 @@ export function SortRuleRow({ label, }) - const renderHeader = () => ( -
onToggleCollapse(rule.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - handleKeyboardActivation(event, () => onToggleCollapse(rule.id)) - }} - > -
- - {rule.collapsed && rule.column - ? formatDisplayText(getColumnLabel(rule.column), { - workflowSearchHighlight: getLabelHighlight('column', getColumnLabel(rule.column)), - }) - : `Sort ${index + 1}`} - - {rule.collapsed && rule.column && ( - - {formatDisplayText(getDirectionLabel(rule.direction), { - workflowSearchHighlight: getLabelHighlight( - 'direction', - getDirectionLabel(rule.direction) - ), - })} - - )} -
-
e.stopPropagation()} + const renderTitle = () => ( + + - - -
-
+ {rule.collapsed && rule.column + ? formatDisplayText(getColumnLabel(rule.column), { + workflowSearchHighlight: getLabelHighlight('column', getColumnLabel(rule.column)), + }) + : `Sort ${index + 1}`} + + {rule.collapsed && rule.column && ( + + {formatDisplayText(getDirectionLabel(rule.direction), { + workflowSearchHighlight: getLabelHighlight( + 'direction', + getDirectionLabel(rule.direction) + ), + })} + + )} + + ) + + const renderActions = () => ( + <> + + + ) const renderContent = () => ( -
+ <>
-
+ ) return ( -
onToggleCollapse(rule.id)} > - {renderHeader()} {!rule.collapsed && renderContent()} -
+ ) } diff --git a/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx b/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx new file mode 100644 index 00000000000..48c2002f869 --- /dev/null +++ b/packages/emcn/src/components/collapsible-card/collapsible-card.test.tsx @@ -0,0 +1,94 @@ +/** @vitest-environment jsdom */ +import { act, type ReactNode, useState } from 'react' +import { CollapsibleCard } from '@sim/emcn' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' + +let container: HTMLDivElement | null = null +let root: Root | null = null + +function mount(children: ReactNode) { + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + act(() => root?.render(children)) +} + +afterEach(() => { + if (root) act(() => root?.unmount()) + container?.remove() + root = null + container = null +}) + +describe('CollapsibleCard', () => { + it('toggles controlled content by click, Enter and Space and forwards root attributes', () => { + function Example() { + const [collapsed, setCollapsed] = useState(true) + return ( + setCollapsed((value) => !value)} + > + + + ) + } + mount() + const card = container!.querySelector('[data-filter-id="condition-1"]')! + const trigger = card.querySelector('[role="button"]')! + expect(trigger.getAttribute('aria-expanded')).toBe('false') + expect(card.querySelector('input')).toBeNull() + act(() => trigger.click()) + expect(trigger.getAttribute('aria-expanded')).toBe('true') + expect(card.querySelector('input')?.value).toBe('Example') + act(() => trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))) + expect(card.querySelector('input')).toBeNull() + act(() => trigger.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true }))) + expect(card.querySelector('input')).not.toBeNull() + }) + + it('keeps enabled and disabled actions outside the collapse target', () => { + const toggle = vi.fn() + const add = vi.fn() + const remove = vi.fn() + const parentClick = vi.fn() + mount( + Long condition name} + badge={Text} + collapsed + onToggleCollapse={toggle} + onClick={parentClick} + actions={ + <> + + + + } + > + Content + + ) + const trigger = container!.querySelector('[role="button"]')! + const [addButton, deleteButton] = container!.querySelectorAll('button') + expect(trigger.contains(addButton)).toBe(false) + act(() => { + addButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })) + addButton.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true })) + addButton.click() + deleteButton.click() + }) + expect(add).toHaveBeenCalledTimes(1) + expect(remove).not.toHaveBeenCalled() + expect(toggle).not.toHaveBeenCalled() + expect(parentClick).not.toHaveBeenCalled() + }) +}) diff --git a/packages/emcn/src/components/collapsible-card/collapsible-card.tsx b/packages/emcn/src/components/collapsible-card/collapsible-card.tsx index 661bab113c9..7dbc6a76b8a 100644 --- a/packages/emcn/src/components/collapsible-card/collapsible-card.tsx +++ b/packages/emcn/src/components/collapsible-card/collapsible-card.tsx @@ -5,47 +5,65 @@ import { cn } from '../../lib/cn' import { handleKeyboardActivation } from '../../lib/keyboard' import { OverflowText, overflowTextClipClass } from '../overflow-text/overflow-text' -export interface CollapsibleCardProps { +export interface CollapsibleCardProps + extends Omit, 'title' | 'children'> { /** Header label rendered with the standard fade-only overflow treatment. */ title: React.ReactNode /** Optional trailing header content, e.g. a type `Badge`. */ badge?: React.ReactNode + /** Header actions, outside the collapse target and arranged with standard spacing. */ + actions?: React.ReactNode collapsed: boolean onToggleCollapse: () => void /** Body content, shown when expanded. */ children: React.ReactNode - className?: string } /** * A collapsible field card: a `--surface-4` header (click / keyboard to toggle) * with a fade-clipped title + optional badge, over a `--surface-2` body. Shared by * the workflow input-mapping rows and the enrichment output-column config. + * + * @example + * Add condition}> + * {fields} + * */ export function CollapsibleCard({ title, badge, + actions, collapsed, onToggleCollapse, children, className, + ...props }: CollapsibleCardProps) { return (
-
handleKeyboardActivation(event, onToggleCollapse)} - > -
+
+
{ + if (event.target !== event.currentTarget) return + handleKeyboardActivation(event, onToggleCollapse) + }} + > {typeof title === 'string' || typeof title === 'number' ? ( + {actions && ( +
event.stopPropagation()} + > + {actions} +
+ )}
{!collapsed && (
From 4692d237532fda1bd9ef9c4660283ade8c789946 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos <157128530+BillLeoutsakosvl346@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:14:39 -0700 Subject: [PATCH 8/8] improvement(ui): compose horizontal and credential fields in EMCN (#8164) * improvement(ui): compose horizontal and credential fields in EMCN * Share deploy chat field error treatment (#8255) * refactor(ui): share deploy chat field error treatment * refactor(ui): name deploy error props --------- Co-authored-by: Bill Leoutsakos --------- Co-authored-by: Bill Leoutsakos --- .claude/rules/emcn-components.md | 4 +- .cursor/rules/emcn-components.mdc | 4 +- .../[executionId]/resume-page-client.tsx | 3 +- .../client-credential-account-modal.tsx | 3 +- .../mcp-server-form-modal.tsx | 3 +- .../no-organization-view.tsx | 22 +++-- .../lock-settings-modal.tsx | 2 +- .../components/chat/chat-field-error.test.tsx | 86 +++++++++++++++++++ .../deploy-modal/components/chat/chat.tsx | 34 +++++--- .../components/chip-input/chip-input.test.tsx | 2 + .../components/chip-modal/chip-modal.test.tsx | 63 +++++++++++++- .../src/components/chip-modal/chip-modal.tsx | 74 +++++++++++++--- .../chip-textarea/chip-textarea.tsx | 16 +++- packages/emcn/src/components/index.ts | 1 + 14 files changed, 270 insertions(+), 47 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat-field-error.test.tsx diff --git a/.claude/rules/emcn-components.md b/.claude/rules/emcn-components.md index 8c8b468d246..0342cadb8cb 100644 --- a/.claude/rules/emcn-components.md +++ b/.claude/rules/emcn-components.md @@ -24,10 +24,10 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items - **`Chip` / `ChipLink`** — the pill button (`
- {error &&

{error}

} + {error && {error}}

{isEditingExisting && value ? ( <> @@ -745,9 +757,7 @@ function AuthSelector({ } /> {canRevealPassword && revealPasswordMutation.isError && ( -

- Failed to load the current password -

+ Failed to load the current password )}

{getPasswordHelperText(hasExistingPassword)} @@ -772,7 +782,7 @@ function AuthSelector({

)} - {error &&

{error}

} + {error && {error}}
) } diff --git a/packages/emcn/src/components/chip-input/chip-input.test.tsx b/packages/emcn/src/components/chip-input/chip-input.test.tsx index a1d70082ff6..542ce50f165 100644 --- a/packages/emcn/src/components/chip-input/chip-input.test.tsx +++ b/packages/emcn/src/components/chip-input/chip-input.test.tsx @@ -95,6 +95,7 @@ describe('chip form controls', () => { { expect(input.parentElement?.className).toContain('h-9') expect(input.parentElement?.className).toContain('border-[var(--text-error)]') expect(textareaRef.current?.rows).toBe(3) + expect(textareaRef.current?.className).toContain('font-mono') expect(textareaRef.current?.className).toContain('min-h-[80px]') act(() => container?.querySelector('button')?.click()) expect(submit).not.toHaveBeenCalled() diff --git a/packages/emcn/src/components/chip-modal/chip-modal.test.tsx b/packages/emcn/src/components/chip-modal/chip-modal.test.tsx index 69f8ab0793e..d741405d056 100644 --- a/packages/emcn/src/components/chip-modal/chip-modal.test.tsx +++ b/packages/emcn/src/components/chip-modal/chip-modal.test.tsx @@ -280,6 +280,61 @@ describe('ChipModalBody', () => { }) }) +describe('ChipModalField composition', () => { + it.each(['vertical', 'horizontal'] as const)( + 'retains labels and feedback in %s fields', + (orientation) => { + mount( + Help} + orientation={orientation} + value='Review' + onChange={() => {}} + required + error='Enter a valid name' + hint='Hidden while invalid' + /> + ) + const input = container!.querySelector('input')! + expect(input.labels?.[0].textContent).toBe('Name*') + expect(input.getAttribute('aria-required')).toBe('true') + expect(input.getAttribute('aria-invalid')).toBe('true') + const error = document.getElementById(input.getAttribute('aria-describedby')!)! + expect(error.textContent).toBe('Enter a valid name') + expect(error.getAttribute('role')).toBe('alert') + expect(container!.textContent).not.toContain('Hidden while invalid') + expect(input.labels?.[0].contains(buttonByText('Help'))).toBe(false) + act(() => input.focus()) + expect(document.activeElement).toBe(input) + } + ) + + it('forwards monospace and height settings to a labeled textarea', () => { + mount( + {}} + mono + minHeight={120} + rows={4} + hint='Enter JSON' + /> + ) + const textarea = container!.querySelector('textarea')! + expect(textarea.labels?.[0].textContent).toBe('JSON') + expect(textarea.className).toContain('font-mono') + expect(textarea.style.minHeight).toBe('120px') + expect(textarea.rows).toBe(4) + expect(document.getElementById(textarea.getAttribute('aria-describedby')!)?.textContent).toBe( + 'Enter JSON' + ) + }) +}) + describe('ChipModalField file actions', () => { it('names each upload action with its field title', () => { mount( @@ -501,7 +556,13 @@ describe('ChipModal default actions', () => { {}} srTitle='Visible action'> {}}>Visible action - {}} /> + {}} + />