fix(editor): restore caret alignment and the intended type scale across the panel (#6297)

* fix(editor): restore caret alignment and the intended type scale across the panel

Four distinct defects, all surfaced while testing the workflow editor.

**Caret drift in the Start block's Description field.** Its overlay mirror was
built differently from every sibling field: `overflow-hidden` + `truncate` and
no scroll synchronisation, where the working fields use `overflow-x-auto` +
`whitespace-pre` + `syncOverlayScroll`. Once the value passed the visible width
the input scrolled and carried the caret with it while the overlay stayed pinned
at the first character, so the gap grew as you typed. It now has the same
plumbing as its siblings, on its own ref maps so it cannot collide with the value
overlay.

**Overlay mirrors left at 500 over 400 inputs.** #6291 dropped emcn Input and
Textarea to the inherited weight, but the canvas files that mirror them were
reverted from that PR, so 11 overlays kept a hardcoded `font-medium`. A mirror
that renders heavier than the input beneath it misaligns by the weight delta on
every character. All 11 realigned.

**Input text tracking differently from its label.** The UA stylesheet resets form
controls to `letter-spacing: normal`, so inside `.workspace-root` (0.02em) an
input diverged from surrounding text — and from its own overlay — by 0.28px per
character. emcn Input and Textarea now carry `[letter-spacing:inherit]`, which
fixes every mirrored input at the source rather than per call site.

**Weights and type sizes that changed meaning under #6241.** That PR deleted the
tailwind remap of `font-medium` (440 light / 480 dark) without migrating the
~505 call sites written against it, so untouched code jumped to a stock 500. The
panel's editor, toolbar, chat and connections surfaces are swept back to the
inherited weight. The Chat header was also visibly taller than Toolbar and Editor
purely because it used `text-[14px]` — font-size with no paired line-height —
against otherwise byte-identical containers; it and the panel's two other
arbitrary sizes now use named tokens.

Also fixes five JSX conditionals in the workflow MCP settings page that had lost
their braces, so `canManage && ()` rendered as literal text under the server
detail tab.

Verified: typecheck 0, biome clean, 18644/18645 vitest passing (the one failure
is a missing `rg` binary and predates this branch).

* fix(emcn): give chip text fields the same tracking as their mirrors

An audit of the previous commit found the letter-spacing fix was incomplete: it
landed on `Input`/`Textarea` but not on the chip family, so `ChipInput` and
`ChipTextarea` kept the UA `letter-spacing: normal` while any overlay mirroring
them inherited the ambient tracking.

The MCP server form modal is a live instance — its shared `FormattedInput` layers
a transparent `ChipInput` under a visible div, across the server URL and both
header fields, whose values are long by nature. The caret separated from the text
by roughly 0.28px per character.

Fixed on `chipFieldTextClass` rather than the call site, so every chip field
matches its mirror the way `Input`/`Textarea` already do.

* fix(editor): restore the strong-text variant a class sweep welded together

The font-weight sweep in 94c9f1f68 used a blanket sed, which turned

  [&_strong]:font-medium [&_strong]:text-[var(--text-primary)]

into [&_strong]:[&_strong]:text-[var(--text-primary)] — a chained variant
matching a <strong> inside a <strong>, so ordinary strong text in trigger setup
instructions lost its color.

The removed weight was also load-bearing rather than decorative. Preflight sets
b/strong to font-weight: bolder, so against a 400 body a bare <strong> lands near
700; the class was holding it down to 500. Deleting it made that text heavier,
the inverse of the sweep's intent — the same UA-default trap as <th>. Both
variants are restored.

Swept for the same damage: no chained [&…]:[&…] variants remain across apps/sim
or packages, and this was the only variant-scoped weight the sed touched.

Found independently by Greptile and Cursor Bugbot.
This commit is contained in:
Waleed
2026-08-05 15:04:30 -07:00
committed by GitHub
parent 0bef1c3099
commit 2ba248472e
36 changed files with 376 additions and 341 deletions
@@ -630,235 +630,239 @@ function ServerDetailView({
</div>
</div>
</SettingsPanel>
canManage && (
<ChipConfirmModal
open={!!toolToDelete}
onOpenChange={(open) => !open && setToolToDelete(null)}
srTitle='Remove Workflow'
title='Remove Workflow'
text={[
'Are you sure you want to remove ',
{ text: toolToDelete?.toolName ?? 'this workflow', bold: true },
' from this server? The workflow will remain deployed and can be added back later.',
]}
confirm={{
label: 'Remove',
onClick: handleDeleteTool,
pending: deleteToolMutation.isPending,
pendingLabel: 'Removing...',
}}
/>
)canManage && (
<ChipModal
open={!!toolToView}
onOpenChange={(open) => {
if (!open) {
setToolToView(null)
setEditingDescription('')
setEditingParameterDescriptions({})
}
}}
srTitle={toolToView?.toolName ?? 'Edit Tool'}
>
<ChipModalHeader onClose={() => setToolToView(null)}>
{toolToView?.toolName}
</ChipModalHeader>
<ChipModalBody>
<ChipModalField
type='textarea'
title='Description'
value={editingDescription}
onChange={setEditingDescription}
placeholder='Describe what this tool does...'
minHeight={80}
/>
<ChipModalField type='custom' title='Parameters'>
{(() => {
const schema = toolToView?.parameterSchema as
| { properties?: Record<string, { type?: string; description?: string }> }
| undefined
const properties = schema?.properties
const hasParams = properties && Object.keys(properties).length > 0
return hasParams ? (
<div className='flex flex-col gap-2'>
{Object.entries(properties).map(([name, prop]) => (
<div
key={name}
className='overflow-hidden rounded-sm border border-[var(--border-1)]'
>
<div className='flex items-center justify-between bg-[var(--surface-4)] px-2.5 py-[5px]'>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-base'>
{name}
</span>
<Badge variant='type' size='sm'>
{prop.type || 'any'}
</Badge>
</div>
</div>
<div className='rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 pt-1.5 pb-2.5'>
<div className='flex flex-col gap-1.5'>
<Label>Description</Label>
<ChipInput
value={editingParameterDescriptions[name] || ''}
onChange={(e) =>
setEditingParameterDescriptions((prev) => ({
...prev,
[name]: e.target.value,
}))
}
placeholder={`Enter description for ${name}`}
/>
</div>
</div>
</div>
))}
</div>
) : (
<p className='text-[var(--text-muted)] text-sm'>
No inputs configured for this workflow.
</p>
)
})()}
</ChipModalField>
</ChipModalBody>
<ChipModalFooter
onCancel={() => setToolToView(null)}
primaryAction={{
label: updateToolMutation.isPending ? 'Saving...' : 'Save',
onClick: handleSaveToolEdit,
disabled: isSaveToolDisabled,
{canManage && (
<ChipConfirmModal
open={!!toolToDelete}
onOpenChange={(open) => !open && setToolToDelete(null)}
srTitle='Remove Workflow'
title='Remove Workflow'
text={[
'Are you sure you want to remove ',
{ text: toolToDelete?.toolName ?? 'this workflow', bold: true },
' from this server? The workflow will remain deployed and can be added back later.',
]}
confirm={{
label: 'Remove',
onClick: handleDeleteTool,
pending: deleteToolMutation.isPending,
pendingLabel: 'Removing...',
}}
/>
</ChipModal>
)canManage && (
<ChipModal
open={showAddWorkflow}
onOpenChange={(open) => {
if (!open) {
setShowAddWorkflow(false)
setSelectedWorkflowId(null)
}
}}
srTitle='Add Workflow'
>
<ChipModalHeader
onClose={() => {
setShowAddWorkflow(false)
setSelectedWorkflowId(null)
)}
{canManage && (
<ChipModal
open={!!toolToView}
onOpenChange={(open) => {
if (!open) {
setToolToView(null)
setEditingDescription('')
setEditingParameterDescriptions({})
}
}}
srTitle={toolToView?.toolName ?? 'Edit Tool'}
>
Add Workflow
</ChipModalHeader>
<ChipModalBody>
<p className='px-2 text-[var(--text-secondary)] text-sm'>
Select a deployed workflow to add to this MCP server. The workflow will be available as
a tool.
</p>
<ChipModalField type='custom' title='Select Workflow'>
<ChipSelect
options={workflowOptions}
value={selectedWorkflowId || undefined}
onChange={(value: string) => setSelectedWorkflowId(value)}
placeholder='Select a workflow...'
searchable
searchPlaceholder='Search workflows...'
disabled={addToolMutation.isPending}
fullWidth
dropdownWidth='trigger'
align='start'
displayLabel={selectedWorkflow?.name}
<ChipModalHeader onClose={() => setToolToView(null)}>
{toolToView?.toolName}
</ChipModalHeader>
<ChipModalBody>
<ChipModalField
type='textarea'
title='Description'
value={editingDescription}
onChange={setEditingDescription}
placeholder='Describe what this tool does...'
minHeight={80}
/>
</ChipModalField>
<ChipModalError>
{addToolMutation.isError
? addToolMutation.error?.message || 'Failed to add workflow'
: null}
</ChipModalError>
</ChipModalBody>
<ChipModalFooter
onCancel={() => {
setShowAddWorkflow(false)
setSelectedWorkflowId(null)
}}
primaryAction={{
label: addToolMutation.isPending ? 'Adding...' : 'Add Workflow',
onClick: handleAddWorkflow,
disabled: !selectedWorkflowId || addToolMutation.isPending,
}}
/>
</ChipModal>
)canManage && (
<ChipModal
open={showEditServer}
onOpenChange={(open) => {
if (!open) {
setShowEditServer(false)
}
}}
srTitle='Edit Server'
>
<ChipModalHeader onClose={() => setShowEditServer(false)}>Edit Server</ChipModalHeader>
<ChipModalBody>
<ChipModalField
type='input'
title='Server name'
required
value={editServerName}
onChange={setEditServerName}
placeholder='e.g., My MCP Server'
<ChipModalField type='custom' title='Parameters'>
{(() => {
const schema = toolToView?.parameterSchema as
| { properties?: Record<string, { type?: string; description?: string }> }
| undefined
const properties = schema?.properties
const hasParams = properties && Object.keys(properties).length > 0
return hasParams ? (
<div className='flex flex-col gap-2'>
{Object.entries(properties).map(([name, prop]) => (
<div
key={name}
className='overflow-hidden rounded-sm border border-[var(--border-1)]'
>
<div className='flex items-center justify-between bg-[var(--surface-4)] px-2.5 py-[5px]'>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-base'>
{name}
</span>
<Badge variant='type' size='sm'>
{prop.type || 'any'}
</Badge>
</div>
</div>
<div className='rounded-b-[4px] border-[var(--border-1)] border-t bg-[var(--surface-2)] px-2.5 pt-1.5 pb-2.5'>
<div className='flex flex-col gap-1.5'>
<Label>Description</Label>
<ChipInput
value={editingParameterDescriptions[name] || ''}
onChange={(e) =>
setEditingParameterDescriptions((prev) => ({
...prev,
[name]: e.target.value,
}))
}
placeholder={`Enter description for ${name}`}
/>
</div>
</div>
</div>
))}
</div>
) : (
<p className='text-[var(--text-muted)] text-sm'>
No inputs configured for this workflow.
</p>
)
})()}
</ChipModalField>
</ChipModalBody>
<ChipModalFooter
onCancel={() => setToolToView(null)}
primaryAction={{
label: updateToolMutation.isPending ? 'Saving...' : 'Save',
onClick: handleSaveToolEdit,
disabled: isSaveToolDisabled,
}}
/>
<ChipModalField
type='textarea'
title='Description'
value={editServerDescription}
onChange={setEditServerDescription}
placeholder='Describe what this MCP server does (optional)'
minHeight={60}
/>
<ChipModalField type='custom' title='Access'>
<div className='flex flex-col gap-1.5'>
<ButtonGroup
value={editServerIsPublic ? 'public' : 'private'}
onValueChange={(value) => setEditServerIsPublic(value === 'public')}
>
<ButtonGroupItem value='private'>API Key</ButtonGroupItem>
<ButtonGroupItem value='public'>Public</ButtonGroupItem>
</ButtonGroup>
<p className='text-[var(--text-muted)] text-caption'>
{editServerIsPublic
? 'Anyone with the URL can call this server without authentication'
: 'Requests must include your Sim API key in the X-API-Key header'}
</p>
</div>
</ChipModalField>
</ChipModalBody>
<ChipModalFooter
onCancel={() => setShowEditServer(false)}
primaryAction={{
label: updateServerMutation.isPending ? 'Saving...' : 'Save',
onClick: handleSaveServerEdit,
disabled:
!editServerName.trim() ||
updateServerMutation.isPending ||
(editServerName === server.name &&
editServerDescription === (server.description || '') &&
editServerIsPublic === server.isPublic),
</ChipModal>
)}
{canManage && (
<ChipModal
open={showAddWorkflow}
onOpenChange={(open) => {
if (!open) {
setShowAddWorkflow(false)
setSelectedWorkflowId(null)
}
}}
srTitle='Add Workflow'
>
<ChipModalHeader
onClose={() => {
setShowAddWorkflow(false)
setSelectedWorkflowId(null)
}}
>
Add Workflow
</ChipModalHeader>
<ChipModalBody>
<p className='px-2 text-[var(--text-secondary)] text-sm'>
Select a deployed workflow to add to this MCP server. The workflow will be available
as a tool.
</p>
<ChipModalField type='custom' title='Select Workflow'>
<ChipSelect
options={workflowOptions}
value={selectedWorkflowId || undefined}
onChange={(value: string) => setSelectedWorkflowId(value)}
placeholder='Select a workflow...'
searchable
searchPlaceholder='Search workflows...'
disabled={addToolMutation.isPending}
fullWidth
dropdownWidth='trigger'
align='start'
displayLabel={selectedWorkflow?.name}
/>
</ChipModalField>
<ChipModalError>
{addToolMutation.isError
? addToolMutation.error?.message || 'Failed to add workflow'
: null}
</ChipModalError>
</ChipModalBody>
<ChipModalFooter
onCancel={() => {
setShowAddWorkflow(false)
setSelectedWorkflowId(null)
}}
primaryAction={{
label: addToolMutation.isPending ? 'Adding...' : 'Add Workflow',
onClick: handleAddWorkflow,
disabled: !selectedWorkflowId || addToolMutation.isPending,
}}
/>
</ChipModal>
)}
{canManage && (
<ChipModal
open={showEditServer}
onOpenChange={(open) => {
if (!open) {
setShowEditServer(false)
}
}}
srTitle='Edit Server'
>
<ChipModalHeader onClose={() => setShowEditServer(false)}>Edit Server</ChipModalHeader>
<ChipModalBody>
<ChipModalField
type='input'
title='Server name'
required
value={editServerName}
onChange={setEditServerName}
placeholder='e.g., My MCP Server'
/>
<ChipModalField
type='textarea'
title='Description'
value={editServerDescription}
onChange={setEditServerDescription}
placeholder='Describe what this MCP server does (optional)'
minHeight={60}
/>
<ChipModalField type='custom' title='Access'>
<div className='flex flex-col gap-1.5'>
<ButtonGroup
value={editServerIsPublic ? 'public' : 'private'}
onValueChange={(value) => setEditServerIsPublic(value === 'public')}
>
<ButtonGroupItem value='private'>API Key</ButtonGroupItem>
<ButtonGroupItem value='public'>Public</ButtonGroupItem>
</ButtonGroup>
<p className='text-[var(--text-muted)] text-caption'>
{editServerIsPublic
? 'Anyone with the URL can call this server without authentication'
: 'Requests must include your Sim API key in the X-API-Key header'}
</p>
</div>
</ChipModalField>
</ChipModalBody>
<ChipModalFooter
onCancel={() => setShowEditServer(false)}
primaryAction={{
label: updateServerMutation.isPending ? 'Saving...' : 'Save',
onClick: handleSaveServerEdit,
disabled:
!editServerName.trim() ||
updateServerMutation.isPending ||
(editServerName === server.name &&
editServerDescription === (server.description || '') &&
editServerIsPublic === server.isPublic),
}}
/>
</ChipModal>
)}
{canManage && (
<CreateApiKeyModal
open={showCreateApiKeyModal}
onOpenChange={setShowCreateApiKeyModal}
workspaceId={workspaceId}
existingKeyNames={existingKeyNames}
allowPersonalApiKeys={allowPersonalApiKeys}
canManageWorkspaceKeys={canManageWorkspaceKeys}
defaultKeyType={defaultKeyType}
/>
</ChipModal>
)canManage && (
<CreateApiKeyModal
open={showCreateApiKeyModal}
onOpenChange={setShowCreateApiKeyModal}
workspaceId={workspaceId}
existingKeyNames={existingKeyNames}
allowPersonalApiKeys={allowPersonalApiKeys}
canManageWorkspaceKeys={canManageWorkspaceKeys}
defaultKeyType={defaultKeyType}
/>
)
)}
</>
)
}
@@ -503,7 +503,7 @@ export function McpDeploy({
return (
<>
<div className='flex h-full flex-col items-center justify-center gap-3'>
<p className='text-[13px] text-[var(--text-muted)]'>
<p className='text-[var(--text-muted)] text-small'>
Create an MCP Server to expose your workflows as tools.
</p>
<Button variant='tertiary' onClick={() => setShowCreateModal(true)}>
@@ -92,7 +92,7 @@ export function FieldItem({
>
<span
className={clsx(
'min-w-0 flex-1 truncate font-medium',
'min-w-0 flex-1 truncate',
'text-[var(--text-secondary)] group-hover:text-[var(--text-primary)]'
)}
>
@@ -161,7 +161,7 @@ function ConnectionItem({
</div>
<span
className={clsx(
'truncate font-medium',
'truncate',
'text-[var(--text-secondary)] group-hover:text-[var(--text-primary)]'
)}
>
@@ -78,7 +78,7 @@ function CheckboxItem({
/>
<Label
htmlFor={`${blockId}-${option.id}`}
className='cursor-pointer font-medium font-sans text-[var(--text-primary)] text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50'
className='cursor-pointer font-sans text-[var(--text-primary)] text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50'
>
{formatDisplayText(option.label, { workflowSearchHighlight })}
</Label>
@@ -955,7 +955,7 @@ export function ConditionInput({
: 'rounded-t-[4px] border-[var(--border-1)] border-b'
)}
>
<span className='font-medium text-[var(--text-tertiary)] text-sm'>
<span className='text-[var(--text-tertiary)] text-sm'>
{isRouterMode ? `Route ${index + 1}` : block.title}
</span>
<div className='flex items-center gap-2'>
@@ -1105,7 +1105,7 @@ export function ConditionInput({
}}
placeholder='Describe when this route should be taken...'
disabled={disabled || isPreview}
className='min-h-[100px] resize-none rounded-none border-0 px-3 py-2 text-sm text-transparent caret-foreground placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
className='min-h-[100px] resize-none rounded-none border-0 px-3 py-2 text-sm text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
rows={4}
style={{ height: `${getRouterHeight(block.id)}px` }}
/>
@@ -453,7 +453,7 @@ export function CredentialSelector({
{needsUpdate && (
<div className='mt-2 flex flex-col gap-1 rounded-sm border bg-[var(--surface-2)] px-2 py-1.5'>
<div className='flex items-center font-medium text-caption'>
<div className='flex items-center text-caption'>
<span className='mr-1.5 inline-block size-[6px] rounded-xs bg-amber-500' />
Additional permissions required
</div>
@@ -471,7 +471,7 @@ export function CredentialSelector({
})
setShowOAuthModal(true)
}}
className='w-full px-2 py-1 font-medium text-caption'
className='w-full px-2 py-1 text-caption'
>
Update access
</Button>
@@ -211,7 +211,7 @@ export function DocumentTagEntry({
const tagCount = tags.filter((t) => t.tagName?.trim()).length
return (
<div className='space-y-1'>
<Label className='font-medium text-muted-foreground text-xs'>Document Tags</Label>
<Label className='text-muted-foreground text-xs'>Document Tags</Label>
<div className='text-muted-foreground text-sm'>
{tagCount > 0 ? `${tagCount} tag(s) configured` : 'No tags'}
</div>
@@ -223,7 +223,7 @@ export function DocumentTagEntry({
return (
<div className='flex h-32 items-center justify-center rounded-sm border border-[var(--border-1)] border-dashed bg-[var(--surface-3)] dark:bg-[var(--code-bg)]'>
<div className='text-center'>
<p className='font-medium text-[var(--text-secondary)] text-sm'>
<p className='text-[var(--text-secondary)] text-sm'>
No tags defined for this knowledge base
</p>
<p className='mt-1 text-[var(--text-muted)] text-xs'>
@@ -250,7 +250,7 @@ export function DocumentTagEntry({
}}
>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-sm'>
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
{tag.collapsed ? tag.tagName || `Tag ${index + 1}` : `Tag ${index + 1}`}
</span>
{tag.collapsed && tag.tagName && (
@@ -334,14 +334,14 @@ export function DocumentTagEntry({
disabled={isReadOnly}
autoComplete='off'
placeholder={placeholder}
className='allow-scroll w-full overflow-auto text-transparent caret-foreground'
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [letter-spacing:inherit]'
/>
<div
ref={(el) => {
if (el) overlayRefs.current[cellKey] = el
}}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!isReadOnly && 'pointer-events-none'
)}
>
@@ -139,7 +139,7 @@ export function EvalInput({
const renderMetricHeader = (metric: EvalMetric, index: number) => (
<div className='flex items-center justify-between overflow-hidden rounded-t-[4px] border-[var(--border-1)] border-b bg-[var(--surface-4)] px-2.5 py-[5px]'>
<span className='font-medium text-[var(--text-tertiary)] text-sm'>Metric {index + 1}</span>
<span className='text-[var(--text-tertiary)] text-sm'>Metric {index + 1}</span>
<div className='flex items-center gap-2'>
<Tooltip.Root key={`add-${metric.id}`}>
<Tooltip.Trigger asChild>
@@ -194,7 +194,7 @@ export function EvalInput({
onChange={(e) => updateMetric(metric.id, 'name', e.target.value)}
placeholder='Accuracy'
disabled={isPreview || disabled}
className='text-transparent caret-foreground placeholder:text-muted-foreground/50'
className='text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50'
/>
<div
className={cn(
@@ -244,7 +244,7 @@ export function EvalInput({
placeholder='How accurate is the response?'
disabled={isPreview || disabled}
className={cn(
'min-h-[80px] whitespace-pre-wrap text-transparent caret-foreground'
'min-h-[80px] whitespace-pre-wrap text-transparent caret-foreground [letter-spacing:inherit]'
)}
rows={3}
/>
@@ -253,7 +253,7 @@ export function EvalInput({
if (el) descriptionOverlayRefs.current[metric.id] = el
}}
className={cn(
'absolute inset-0 overflow-auto bg-transparent px-2 py-2 font-medium font-sans text-[var(--code-foreground)] text-sm',
'absolute inset-0 overflow-auto bg-transparent px-2 py-2 font-sans text-[var(--code-foreground)] text-sm',
!(isPreview || disabled) && 'pointer-events-none'
)}
>
@@ -300,9 +300,9 @@ export function EvalInput({
autoComplete='off'
data-form-type='other'
name='eval-range-min'
className='text-transparent caret-foreground'
className='text-transparent caret-foreground [letter-spacing:inherit]'
/>
<div className='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-medium font-sans text-sm'>
<div className='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-sans text-sm'>
{formatDisplayText(String(metric.range.min ?? ''), {
workflowSearchHighlight: getMetricSearchHighlight(index, ['range', 'min']),
})}
@@ -321,9 +321,9 @@ export function EvalInput({
autoComplete='off'
data-form-type='other'
name='eval-range-max'
className='text-transparent caret-foreground'
className='text-transparent caret-foreground [letter-spacing:inherit]'
/>
<div className='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-medium font-sans text-sm'>
<div className='pointer-events-none absolute inset-0 flex items-center truncate px-2 py-1.5 font-sans text-sm'>
{formatDisplayText(String(metric.range.max ?? ''), {
workflowSearchHighlight: getMetricSearchHighlight(index, ['range', 'max']),
})}
@@ -114,7 +114,7 @@ export function FilterRuleRow({
}}
>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-sm'>
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
{rule.collapsed && rule.column
? formatDisplayText(getColumnLabel(rule.column), {
workflowSearchHighlight: getLabelHighlight('column', getColumnLabel(rule.column)),
@@ -175,12 +175,12 @@ export function FilterRuleRow({
disabled={isReadOnly}
autoComplete='off'
placeholder='Enter value'
className='allow-scroll w-full overflow-auto text-transparent caret-foreground'
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [letter-spacing:inherit]'
/>
<div
ref={overlayRef}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!isReadOnly && 'pointer-events-none'
)}
>
@@ -24,20 +24,12 @@ interface SelectedCountDisplayProps {
function SelectedCountDisplay({ noneSelected, allSelected, count }: SelectedCountDisplayProps) {
if (noneSelected) {
return (
<span className='truncate font-medium text-[var(--text-muted)] text-sm'>None selected</span>
)
return <span className='truncate text-[var(--text-muted)] text-sm'>None selected</span>
}
if (allSelected) {
return (
<span className='truncate font-medium text-[var(--text-primary)] text-sm'>All selected</span>
)
return <span className='truncate text-[var(--text-primary)] text-sm'>All selected</span>
}
return (
<span className='truncate font-medium text-[var(--text-primary)] text-sm'>
{count} selected
</span>
)
return <span className='truncate text-[var(--text-primary)] text-sm'>{count} selected</span>
}
interface GroupedCheckboxListProps {
@@ -130,7 +122,7 @@ export function GroupedCheckboxList({
disabled={disabled}
onClick={() => setOpen(true)}
className={cn(
'flex w-full cursor-pointer items-center justify-between rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-medium font-sans text-[var(--text-primary)] text-sm outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-[var(--surface-5)]',
'flex w-full cursor-pointer items-center justify-between rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-sans text-[var(--text-primary)] text-sm outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 dark:bg-[var(--surface-5)]',
'hover-hover:bg-[var(--surface-active)]'
)}
>
@@ -168,7 +160,7 @@ export function GroupedCheckboxList({
/>
<label
htmlFor='select-all'
className='cursor-pointer font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
className='cursor-pointer text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
>
Select all entities
</label>
@@ -132,7 +132,7 @@ export function InputMapping({
return (
<div className='flex h-32 items-center justify-center rounded-sm border border-[var(--border-1)] border-dashed bg-[var(--surface-3)] dark:bg-[var(--code-bg)]'>
<div className='text-center'>
<p className='font-medium text-[var(--text-secondary)] text-sm'>No workflow selected</p>
<p className='text-[var(--text-secondary)] text-sm'>No workflow selected</p>
<p className='mt-1 text-[var(--text-muted)] text-xs'>
Select a workflow above to configure inputs
</p>
@@ -274,7 +274,9 @@ function InputMappingField({
placeholder='Enter value or reference'
disabled={disabled}
autoComplete='off'
className={cn('allow-scroll w-full overflow-auto text-transparent caret-foreground')}
className={cn(
'allow-scroll w-full overflow-auto text-transparent caret-foreground [letter-spacing:inherit]'
)}
style={{ overflowX: 'auto' }}
/>
<div
@@ -282,7 +284,7 @@ function InputMappingField({
if (el) overlayRefs.current.set(fieldId, el)
}}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!disabled && 'pointer-events-none'
)}
style={{ overflowX: 'auto' }}
@@ -203,7 +203,7 @@ export function KnowledgeBaseSelector({
className='inline-flex items-center rounded-md border border-[color-mix(in_srgb,var(--brand-knowledge)_20%,transparent)] bg-[color-mix(in_srgb,var(--brand-knowledge)_10%,transparent)] px-2 py-1 text-xs'
>
<PackageSearchIcon className='mr-1 size-3 text-[var(--brand-knowledge)]' />
<span className='font-medium text-[var(--brand-knowledge)]'>
<span className='text-[var(--brand-knowledge)]'>
{formatDisplayText(labelOf(kb), { workflowSearchHighlight })}
</span>
{!disabled && !isPreview && (
@@ -219,7 +219,7 @@ export function KnowledgeTagFilters({
return (
<div className='space-y-1'>
<Label className='font-medium text-muted-foreground text-xs'>Tag Filters</Label>
<Label className='text-muted-foreground text-xs'>Tag Filters</Label>
<div className='text-muted-foreground text-sm'>
{appliedFilters > 0 ? `${appliedFilters} filter(s) applied` : 'No filters'}
</div>
@@ -243,7 +243,7 @@ export function KnowledgeTagFilters({
}}
>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-sm'>
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
{filter.collapsed ? filter.tagName || `Filter ${index + 1}` : `Filter ${index + 1}`}
</span>
{filter.collapsed && filter.tagName && (
@@ -329,14 +329,14 @@ export function KnowledgeTagFilters({
disabled={isReadOnly}
autoComplete='off'
placeholder={placeholder}
className='allow-scroll w-full overflow-auto text-transparent caret-foreground'
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [letter-spacing:inherit]'
/>
<div
ref={(el) => {
if (el) overlayRefs.current[cellKey] = el
}}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!isReadOnly && 'pointer-events-none'
)}
>
@@ -333,7 +333,7 @@ export function LongInput({
<Textarea
ref={setRefs}
className={cn(
'allow-scroll box-border min-h-full w-full resize-none text-transparent caret-foreground placeholder:text-muted-foreground/50',
'allow-scroll box-border min-h-full w-full resize-none text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50',
wandHook.isStreaming && 'pointer-events-none cursor-not-allowed opacity-50'
)}
rows={rows ?? DEFAULT_ROWS}
@@ -357,7 +357,7 @@ export function LongInput({
<div
ref={overlayRef}
className={cn(
'absolute inset-0 box-border overflow-auto whitespace-pre-wrap break-words border border-transparent bg-transparent px-2 py-2 font-medium font-sans text-sm',
'absolute inset-0 box-border overflow-auto whitespace-pre-wrap break-words border border-transparent bg-transparent px-2 py-2 font-sans text-sm',
(isPreview || disabled) && 'opacity-50',
!(isPreview || disabled) && 'pointer-events-none'
)}
@@ -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 font-medium 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 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)]'
)}
@@ -714,7 +714,7 @@ export function MessagesInput({
ref={(el) => {
textareaRefs.current[fieldId] = el
}}
className='relative z-[2] m-0 box-border h-auto min-h-[80px] w-full resize-none overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-words border-none bg-transparent p-2 font-medium font-sans text-sm text-transparent leading-[1.5] caret-[var(--text-primary)] outline-none [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-[var(--text-muted)] focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed [&::-webkit-scrollbar]:hidden'
className='relative z-[2] m-0 box-border h-auto min-h-[80px] w-full resize-none overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-words border-none bg-transparent p-2 font-sans text-sm text-transparent leading-[1.5] caret-[var(--text-primary)] outline-none [-ms-overflow-style:none] [letter-spacing:inherit] [scrollbar-width:none] placeholder:text-[var(--text-muted)] focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed [&::-webkit-scrollbar]:hidden'
placeholder='Enter message content...'
value={message.content}
onChange={fieldHandlers.onChange}
@@ -755,7 +755,7 @@ export function MessagesInput({
overlayRefs.current[fieldId] = el
}}
className={cn(
'absolute top-0 left-0 z-[1] m-0 box-border w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-words border-none bg-transparent p-2 font-medium font-sans text-[var(--text-primary)] text-sm leading-[1.5] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
'absolute top-0 left-0 z-[1] m-0 box-border w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-words border-none bg-transparent p-2 font-sans text-[var(--text-primary)] text-sm leading-[1.5] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
!(isPreview || disabled) && 'pointer-events-none'
)}
>
@@ -370,7 +370,7 @@ export const ShortInput = memo(function ShortInput({
<>
<Input
ref={ref as React.RefObject<HTMLInputElement>}
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-muted-foreground/50 [&::-webkit-scrollbar]:hidden'
className='allow-scroll w-full overflow-auto text-transparent caret-foreground [-ms-overflow-style:none] [letter-spacing:inherit] [scrollbar-width:none] placeholder:text-muted-foreground/50 [&::-webkit-scrollbar]:hidden'
readOnly={readOnly}
placeholder={placeholder ?? ''}
type='text'
@@ -393,7 +393,7 @@ export const ShortInput = memo(function ShortInput({
<div
ref={overlayRef}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 pr-3 font-medium font-sans text-foreground text-sm [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 pr-3 font-sans text-foreground text-sm [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
(isPreview || disabled) && 'opacity-50',
!(isPreview || disabled) && 'pointer-events-none'
)}
@@ -168,7 +168,7 @@ export function SkillInput({
<div className='flex size-[16px] flex-shrink-0 items-center justify-center rounded-sm bg-[var(--border-1)]'>
<AgentSkillsIcon className='size-[10px] text-[var(--text-icon)]' />
</div>
<span className='truncate font-medium text-[var(--text-primary)] text-small'>
<span className='truncate text-[var(--text-primary)] text-small'>
{formatDisplayText(skillName, { workflowSearchHighlight })}
</span>
</div>
@@ -63,9 +63,7 @@ export function SlackSetupWizard({
: 'cursor-pointer hover-hover:bg-[var(--surface-6)]'
)}
>
<span className='font-medium font-sans text-[var(--text-primary)] text-sm'>
Setup Slack App
</span>
<span className='font-sans text-[var(--text-primary)] text-sm'>Setup Slack App</span>
<ChevronRight className='size-[14px] text-[var(--text-muted)]' />
</button>
@@ -190,7 +188,7 @@ interface SubStepProps {
function SubStep({ n, children }: SubStepProps) {
return (
<li className='flex gap-2.5'>
<span className='mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--surface-5)] font-medium text-[var(--text-secondary)] text-xs tabular-nums'>
<span className='mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--surface-5)] text-[var(--text-secondary)] text-xs tabular-nums'>
{n}
</span>
<div className='flex-1 text-[var(--text-secondary)] text-sm leading-relaxed'>{children}</div>
@@ -222,7 +220,7 @@ function StepConfigure({
<div className='space-y-1.5'>
<Label
htmlFor={`${blockId}-wizard-bot-name`}
className='font-medium text-[var(--text-secondary)] text-xs'
className='text-[var(--text-secondary)] text-xs'
>
Bot name
</Label>
@@ -292,7 +290,7 @@ function StepCreate({ manifestJson, canCopy }: StepCreateProps) {
: 'cursor-not-allowed opacity-70'
)}
>
<span className='font-medium text-[var(--text-secondary)] text-sm'>
<span className='text-[var(--text-secondary)] text-sm'>
{canCopy ? 'Click to copy manifest' : 'Deploy once to lock in the webhook URL'}
</span>
{canCopy &&
@@ -413,7 +411,7 @@ interface SecretFieldProps {
function SecretField({ id, label, value, onChange, disabled, placeholder }: SecretFieldProps) {
return (
<div className='space-y-1.5'>
<Label htmlFor={id} className='font-medium text-[var(--text-secondary)] text-xs'>
<Label htmlFor={id} className='text-[var(--text-secondary)] text-xs'>
{label}
</Label>
<SecretInput
@@ -488,9 +486,7 @@ function CapabilityGroup({
}: CapabilityGroupProps) {
return (
<div className='space-y-2'>
<div className='font-medium text-[var(--text-muted)] text-xs uppercase tracking-wide'>
{label}
</div>
<div className='text-[var(--text-muted)] text-xs uppercase tracking-wide'>{label}</div>
<div className='flex flex-col gap-y-2.5'>
{capabilities.map((c) => (
<CapabilityRow
@@ -73,7 +73,7 @@ export function SortRuleRow({
}}
>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-sm'>
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
{rule.collapsed && rule.column
? formatDisplayText(getColumnLabel(rule.column), {
workflowSearchHighlight: getLabelHighlight('column', getColumnLabel(rule.column)),
@@ -111,6 +111,8 @@ export function FieldFormat({
const nameInputRefs = useRef<Record<string, HTMLInputElement>>({})
const overlayRefs = useRef<Record<string, HTMLDivElement>>({})
const nameOverlayRefs = useRef<Record<string, HTMLDivElement>>({})
const descriptionInputRefs = useRef<Record<string, HTMLInputElement>>({})
const descriptionOverlayRefs = useRef<Record<string, HTMLDivElement>>({})
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
const [fileFieldModes, setFileFieldModes] = useState<Record<string, 'upload' | 'json'>>({})
@@ -283,6 +285,14 @@ export function FieldFormat({
if (overlay) overlay.scrollLeft = scrollLeft
}
/**
* Syncs scroll position between description input and overlay for text highlighting
*/
const syncDescriptionOverlayScroll = (fieldId: string, scrollLeft: number) => {
const overlay = descriptionOverlayRefs.current[fieldId]
if (overlay) overlay.scrollLeft = scrollLeft
}
/**
* Generates a unique field key for name inputs to avoid collision with value inputs
*/
@@ -313,7 +323,7 @@ export function FieldFormat({
(newValue) => updateField(field.id, 'name', newValue)
)
const inputClassName = cn('text-transparent caret-foreground')
const inputClassName = cn('text-transparent [letter-spacing:inherit] caret-foreground')
return (
<>
@@ -345,7 +355,7 @@ export function FieldFormat({
if (el) nameOverlayRefs.current[field.id] = el
}}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!isReadOnly && 'pointer-events-none'
)}
style={{ scrollbarWidth: 'none' }}
@@ -393,7 +403,7 @@ export function FieldFormat({
}}
>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-sm'>
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
{field.name || `${title} ${index + 1}`}
</span>
{field.name && showType && (
@@ -460,7 +470,7 @@ export function FieldFormat({
(newValue) => updateField(field.id, 'value', newValue)
)
const inputClassName = cn('text-transparent caret-foreground')
const inputClassName = cn('text-transparent [letter-spacing:inherit] caret-foreground')
const tagDropdown = fieldState.showTags && (
<TagDropdown
@@ -483,7 +493,7 @@ export function FieldFormat({
return Array.from({ length: lineCount }, (_, i) => (
<div
key={i}
className='font-medium font-mono text-[var(--text-muted)] text-xs'
className='font-mono text-[var(--text-muted)] text-xs'
style={{ height: `${21}px`, lineHeight: `${21}px` }}
>
{i + 1}
@@ -518,7 +528,7 @@ export function FieldFormat({
return Array.from({ length: lineCount }, (_, i) => (
<div
key={i}
className='font-medium font-mono text-[var(--text-muted)] text-xs'
className='font-mono text-[var(--text-muted)] text-xs'
style={{ height: `${21}px`, lineHeight: `${21}px` }}
>
{i + 1}
@@ -577,7 +587,7 @@ export function FieldFormat({
Array.from({ length: lineCount }, (_, i) => (
<div
key={i}
className='font-medium font-mono text-[var(--text-muted)] text-xs'
className='font-mono text-[var(--text-muted)] text-xs'
style={{ height: `${21}px`, lineHeight: `${21}px` }}
>
{i + 1}
@@ -635,7 +645,7 @@ export function FieldFormat({
if (el) overlayRefs.current[field.id] = el
}}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!isReadOnly && 'pointer-events-none'
)}
style={{ scrollbarWidth: 'none' }}
@@ -692,19 +702,36 @@ export function FieldFormat({
{renderFieldLabel('Description')}
<div className='relative'>
<Input
ref={(el) => {
if (el) descriptionInputRefs.current[field.id] = el
}}
value={field.description ?? ''}
onChange={(e) => updateField(field.id, 'description', e.target.value)}
onScroll={(e) =>
syncDescriptionOverlayScroll(field.id, e.currentTarget.scrollLeft)
}
onPaste={() =>
setTimeout(() => {
const input = descriptionInputRefs.current[field.id]
input && syncDescriptionOverlayScroll(field.id, input.scrollLeft)
}, 0)
}
placeholder={descriptionPlaceholder}
disabled={isReadOnly}
className='text-transparent caret-foreground placeholder:text-muted-foreground/50'
autoComplete='off'
className='allow-scroll w-full overflow-x-auto overflow-y-hidden text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50'
/>
<div
ref={(el) => {
if (el) descriptionOverlayRefs.current[field.id] = el
}}
style={{ scrollbarWidth: 'none' }}
className={cn(
'pointer-events-none absolute inset-0 flex items-center overflow-hidden px-3 text-sm',
'pointer-events-none absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
isReadOnly && 'opacity-50'
)}
>
<span className='truncate'>
<span className='w-full whitespace-pre' style={{ minWidth: 'fit-content' }}>
{formatDisplayText(
field.description ?? '',
accessiblePrefixes
@@ -40,7 +40,7 @@ export function Switch({
/>
<Label
htmlFor={`${blockId}-${subBlockId}`}
className='cursor-pointer font-medium font-sans text-[var(--text-primary)] text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50'
className='cursor-pointer font-sans text-[var(--text-primary)] text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50'
>
{title}
</Label>
@@ -189,7 +189,7 @@ function TableCell({
autoCorrect='off'
autoCapitalize='off'
spellCheck='false'
className='w-full bg-transparent px-2.5 py-2 font-medium text-sm text-transparent leading-[21px] caret-[var(--text-primary)] outline-none placeholder:text-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50'
className='w-full bg-transparent px-2.5 py-2 text-sm text-transparent leading-[21px] caret-[var(--text-primary)] outline-none [letter-spacing:inherit] placeholder:text-[var(--text-muted)] disabled:cursor-not-allowed disabled:opacity-50'
/>
<div
ref={(el) => {
@@ -199,7 +199,7 @@ function TableCell({
data-overlay={cellKey}
className='scrollbar-hide pointer-events-none absolute top-0 right-[10px] bottom-0 left-[10px] overflow-x-auto overflow-y-hidden bg-transparent'
>
<div className='whitespace-pre py-2 font-medium text-[var(--text-primary)] text-sm leading-[21px]'>
<div className='whitespace-pre py-2 text-[var(--text-primary)] text-sm leading-[21px]'>
{formatDisplayText(cellValue, {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
@@ -378,7 +378,7 @@ export function Table({
<th
key={column}
className={cn(
'bg-transparent px-2.5 py-[5px] text-left font-medium text-[var(--text-tertiary)] text-sm',
'bg-transparent px-2.5 py-[5px] text-left text-[var(--text-tertiary)] text-sm',
index < columns.length - 1 && 'border-[var(--border-1)] border-r'
)}
>
@@ -475,7 +475,7 @@ const FolderContentsInner: React.FC<FolderContentsProps> = ({
}
}}
>
<span className='flex-1 truncate font-medium'>{currentNestedTag.display}</span>
<span className='flex-1 truncate'>{currentNestedTag.display}</span>
</PopoverItem>
)}
@@ -845,7 +845,7 @@ const BlockRootTagItem: React.FC<{
}}
>
<TagIcon icon={tagIcon} color={blockColor} />
<span className='flex-1 truncate font-medium'>{blockName}</span>
<span className='flex-1 truncate'>{blockName}</span>
</PopoverItem>
)
}
@@ -33,7 +33,7 @@ export function Text({ blockId, subBlockId, content, className }: TextProps) {
className={`rounded-md border bg-[var(--surface-2)] p-4 shadow-sm ${className || ''}`}
>
<div
className='max-w-none break-words text-[var(--text-secondary)] text-sm [&_a]:text-[var(--brand-secondary)] [&_a]:underline [&_a]:underline-offset-2 [&_a]:hover-hover:brightness-110 [&_code]:rounded [&_code]:bg-[var(--surface-5)] [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[var(--text-tertiary)] [&_code]:text-xs [&_strong]:font-medium [&_strong]:text-[var(--text-primary)] [&_ul]:ml-5 [&_ul]:list-disc [&_ul]:marker:text-[var(--text-muted)]'
className='max-w-none break-words text-[var(--text-secondary)] text-sm [&_a]:text-[var(--brand-secondary)] [&_a]:underline [&_a]:underline-offset-2 [&_a]:hover-hover:brightness-110 [&_code]:rounded [&_code]:bg-[var(--surface-5)] [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[var(--text-tertiary)] [&_code]:text-xs [&_ul]:ml-5 [&_ul]:list-disc [&_ul]:marker:text-[var(--text-muted)] [&_strong]:font-medium [&_strong]:text-[var(--text-primary)]'
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
@@ -237,7 +237,7 @@ export function ToolCredentialSelector({
{needsUpdate && (
<div className='mt-2 flex flex-col gap-1 rounded-sm border bg-[var(--surface-2)] px-2 py-1.5'>
<div className='flex items-center font-medium text-caption'>
<div className='flex items-center text-caption'>
<span className='mr-1.5 inline-block size-[6px] rounded-xs bg-amber-500' />
Additional permissions required
</div>
@@ -255,7 +255,7 @@ export function ToolCredentialSelector({
})
setShowOAuthModal(true)
}}
className='w-full px-2 py-1 font-medium text-caption'
className='w-full px-2 py-1 text-caption'
>
Update access
</Button>
@@ -86,7 +86,7 @@ export function ParameterWithLabel({
return (
<div key={paramId} className='relative min-w-0 space-y-1.5'>
<div className='flex items-center justify-between gap-1.5 pl-0.5'>
<Label className='flex items-baseline gap-1.5 whitespace-nowrap font-medium text-[var(--text-primary)] text-small'>
<Label className='flex items-baseline gap-1.5 whitespace-nowrap text-[var(--text-primary)] text-small'>
{title}
{isRequired && visibility === 'user-only' && <span className='ml-0.5'>*</span>}
</Label>
@@ -1904,7 +1904,7 @@ export const ToolInput = memo(function ToolInput({
/>
)}
</div>
<span className='truncate font-medium text-[var(--text-primary)] text-small'>
<span className='truncate text-[var(--text-primary)] text-small'>
{formatDisplayText(toolDisplayName ?? '', {
workflowSearchHighlight: getToolTitleSearchHighlight(toolIndex),
})}
@@ -1952,7 +1952,7 @@ export const ToolInput = memo(function ToolInput({
>
<PopoverTrigger asChild>
<button
className='flex items-center justify-center font-medium text-[var(--text-tertiary)] text-caption transition-colors hover-hover:text-[var(--text-primary)]'
className='flex items-center justify-center text-[var(--text-tertiary)] text-caption transition-colors hover-hover:text-[var(--text-primary)]'
onClick={(e: React.MouseEvent) => e.stopPropagation()}
aria-label='Tool usage control'
>
@@ -2077,9 +2077,7 @@ export const ToolInput = memo(function ToolInput({
return hasOperations && operationOptions.length > 0 ? (
<div className='relative space-y-1.5'>
<div className='font-medium text-[var(--text-primary)] text-small'>
Operation
</div>
<div className='text-[var(--text-primary)] text-small'>Operation</div>
<Combobox
options={operationOptions
.filter((option) => option.id !== '')
@@ -349,7 +349,7 @@ export function VariablesInput({
d='M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'
/>
</svg>
<p className='mb-1 font-medium text-foreground text-sm'>No variable assignments defined</p>
<p className='mb-1 text-foreground text-sm'>No variable assignments defined</p>
<p className='text-muted-foreground text-xs'>
Add variables in the Variables panel to get started
</p>
@@ -413,7 +413,7 @@ export function VariablesInput({
}}
>
<div className='flex min-w-0 flex-1 items-center gap-2'>
<span className='block truncate font-medium text-[var(--text-tertiary)] text-sm'>
<span className='block truncate text-[var(--text-tertiary)] text-sm'>
{assignment.variableName
? formatDisplayText(assignment.variableName, {
workflowSearchHighlight: variableLabelHighlight,
@@ -569,7 +569,7 @@ export function VariablesInput({
}
disabled={isReadOnly}
className={cn(
'min-h-[120px] font-mono text-sm text-transparent caret-foreground placeholder:text-muted-foreground/50',
'min-h-[120px] font-mono text-sm text-transparent caret-foreground [letter-spacing:inherit] placeholder:text-muted-foreground/50',
dragHighlight[assignment.id] && 'ring-2 ring-blue-500 ring-offset-2'
)}
style={{
@@ -639,7 +639,7 @@ export function VariablesInput({
disabled={isReadOnly}
autoComplete='off'
className={cn(
'allow-scroll w-full overflow-x-auto overflow-y-hidden text-transparent caret-foreground',
'allow-scroll w-full overflow-x-auto overflow-y-hidden text-transparent caret-foreground [letter-spacing:inherit]',
dragHighlight[assignment.id] && 'ring-2 ring-blue-500 ring-offset-2'
)}
onDrop={(e) => handleDrop(e, assignment.id)}
@@ -651,7 +651,7 @@ export function VariablesInput({
if (el) overlayRefs.current[assignment.id] = el
}}
className={cn(
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-medium font-sans text-sm',
'absolute inset-0 flex items-center overflow-x-auto bg-transparent px-2 py-1.5 font-sans text-sm',
!isReadOnly && 'pointer-events-none'
)}
style={{ scrollbarWidth: 'none' }}
@@ -131,7 +131,7 @@ export function SubflowEditor({
data-workflow-search-canonical-id={WORKFLOW_SEARCH_SUBFLOW_FIELD_IDS.type}
className='rounded-md'
>
<Label className='mb-[6.5px] block pl-0.5 font-medium text-[var(--text-primary)] text-small'>
<Label className='mb-[6.5px] block pl-0.5 text-[var(--text-primary)] text-small'>
{currentBlock.type === 'loop' ? 'Loop Type' : 'Parallel Type'}
</Label>
<Combobox
@@ -150,7 +150,7 @@ export function SubflowEditor({
data-workflow-search-canonical-id={configSearchFieldId}
className='rounded-md'
>
<Label className='mb-[6.5px] block pl-0.5 font-medium text-[var(--text-primary)] text-small'>
<Label className='mb-[6.5px] block pl-0.5 text-[var(--text-primary)] text-small'>
{isCountMode
? `${currentBlock.type === 'loop' ? 'Loop' : 'Parallel'} Iterations`
: isConditionMode
@@ -166,9 +166,9 @@ export function SubflowEditor({
onChange={handleSubflowIterationsChange}
onBlur={handleSubflowIterationsBlur}
disabled={!userCanEdit}
className='mb-1 text-transparent caret-[var(--text-primary)]'
className='mb-1 text-transparent caret-[var(--text-primary)] [letter-spacing:inherit]'
/>
<div className='pointer-events-none absolute inset-x-0 top-0 flex h-8 items-center overflow-hidden px-2 font-medium font-sans text-sm'>
<div className='pointer-events-none absolute inset-x-0 top-0 flex h-8 items-center overflow-hidden px-2 font-sans text-sm'>
{formatDisplayText(inputValue, {
workflowSearchHighlight: configSearchHighlight,
})}
@@ -223,7 +223,7 @@ export function SubflowEditor({
data-workflow-search-canonical-id={WORKFLOW_SEARCH_SUBFLOW_FIELD_IDS.batchSize}
className='relative mt-4 rounded-md'
>
<Label className='mb-[6.5px] block pl-0.5 font-medium text-[var(--text-primary)] text-small'>
<Label className='mb-[6.5px] block pl-0.5 text-[var(--text-primary)] text-small'>
Parallel Batch Size
</Label>
<Input
@@ -232,9 +232,9 @@ export function SubflowEditor({
onChange={handleParallelBatchSizeChange}
onBlur={handleParallelBatchSizeBlur}
disabled={!userCanEdit}
className='mb-1 text-transparent caret-[var(--text-primary)]'
className='mb-1 text-transparent caret-[var(--text-primary)] [letter-spacing:inherit]'
/>
<div className='pointer-events-none absolute inset-x-0 top-[26px] flex h-8 items-center overflow-hidden px-2 font-medium font-sans text-sm'>
<div className='pointer-events-none absolute inset-x-0 top-[26px] flex h-8 items-center overflow-hidden px-2 font-sans text-sm'>
{formatDisplayText(batchSizeValue, {
workflowSearchHighlight: batchSizeSearchHighlight,
})}
@@ -283,7 +283,7 @@ export function SubflowEditor({
(!isConnectionsAtMinHeight ? ' rotate-180' : '')
}
/>
<div className='font-medium text-[var(--text-primary)] text-small'>Connections</div>
<div className='text-[var(--text-primary)] text-small'>Connections</div>
</div>
<div className='flex-1 overflow-y-auto overflow-x-hidden px-1.5 pb-2'>
@@ -399,11 +399,11 @@ export function Editor() {
handleCancelRename()
}
}}
className='min-w-0 flex-1 truncate bg-transparent pr-2 font-medium text-[var(--text-primary)] text-sm outline-none'
className='min-w-0 flex-1 truncate bg-transparent pr-2 text-[var(--text-primary)] text-sm outline-none'
/>
) : (
<h2
className='min-w-0 flex-1 cursor-pointer select-none text-ellipsis whitespace-nowrap pr-2 font-medium text-[var(--text-primary)] text-sm [overflow-clip-margin:3px] [overflow:clip]'
className='min-w-0 flex-1 cursor-pointer select-none text-ellipsis whitespace-nowrap pr-2 text-[var(--text-primary)] text-sm [overflow-clip-margin:3px] [overflow:clip]'
title={title}
onDoubleClick={handleStartRename}
onMouseDown={(e) => {
@@ -538,7 +538,7 @@ export function Editor() {
{isWorkflowBlock && childWorkflowId && (
<>
<div className='subblock-content flex flex-col gap-[9.5px]'>
<div className='pl-0.5 font-medium text-[var(--text-primary)] text-small leading-none'>
<div className='pl-0.5 text-[var(--text-primary)] text-small leading-none'>
Workflow Preview
</div>
<div className='relative h-[160px] overflow-hidden rounded-sm border border-[var(--border)]'>
@@ -670,7 +670,7 @@ export function Editor() {
<button
type='button'
onClick={handleToggleAdvancedMode}
className='flex items-center gap-1.5 whitespace-nowrap font-medium text-[var(--text-secondary)] text-small hover-hover:text-[var(--text-primary)]'
className='flex items-center gap-1.5 whitespace-nowrap text-[var(--text-secondary)] text-small hover-hover:text-[var(--text-primary)]'
>
{displayAdvancedOptions
? 'Hide additional fields'
@@ -685,7 +685,7 @@ export function Editor() {
{hasAdvancedOnlyFields && !canEditBlock && displayAdvancedOptions && (
<div className='flex items-center gap-2.5 px-0.5 pt-3.5 pb-3'>
<DashedDividerLine className='flex-1' />
<span className='whitespace-nowrap font-medium text-[var(--text-secondary)] text-small'>
<span className='whitespace-nowrap text-[var(--text-secondary)] text-small'>
Additional fields
</span>
<DashedDividerLine className='flex-1' />
@@ -774,9 +774,7 @@ export function Editor() {
(!isConnectionsAtMinHeight ? ' rotate-180' : '')
}
/>
<div className='font-medium text-[var(--text-primary)] text-small'>
Connections
</div>
<div className='text-[var(--text-primary)] text-small'>Connections</div>
</div>
{/* Connections Content - Always visible */}
@@ -800,7 +800,7 @@ export const Toolbar = memo(
onClick={focusSearch}
onKeyDown={(event) => handleKeyboardActivation(event, focusSearch)}
>
<h2 className='font-medium text-[var(--text-primary)] text-sm'>Toolbar</h2>
<h2 className='text-[var(--text-primary)] text-sm'>Toolbar</h2>
<div className='flex shrink-0 items-center gap-2'>
{!isSearchActive ? (
<Button
@@ -818,7 +818,7 @@ export const Toolbar = memo(
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onBlur={handleSearchBlur}
className='w-full border-none bg-transparent pr-0.5 text-right font-medium text-[var(--text-primary)] text-small placeholder:text-[var(--text-muted)] focus:outline-none'
className='w-full border-none bg-transparent pr-0.5 text-right text-[var(--text-primary)] text-small placeholder:text-[var(--text-muted)] focus:outline-none'
/>
)}
</div>
@@ -841,7 +841,7 @@ export const Panel = memo(function Panel() {
>
{/* Copilot Header */}
<div className='mx-[-1px] flex flex-shrink-0 items-center justify-between gap-2 border border-[var(--border)] bg-[var(--surface-4)] px-3 py-1.5'>
<h2 className='min-w-0 flex-1 truncate font-medium text-[14px] text-[var(--text-primary)]'>
<h2 className='min-w-0 flex-1 truncate text-[var(--text-primary)] text-sm'>
{copilotChatTitle || 'New Chat'}
</h2>
<div className='flex items-center gap-2'>
@@ -862,7 +862,7 @@ export const Panel = memo(function Panel() {
</PopoverTrigger>
<PopoverContent align='end' side='bottom' sideOffset={8} maxHeight={280}>
{copilotChatList.length === 0 ? (
<div className='px-1.5 py-4 text-center text-[12px] text-muted-foreground'>
<div className='px-1.5 py-4 text-center text-caption text-muted-foreground'>
No chats yet
</div>
) : (
@@ -878,7 +878,7 @@ export const Panel = memo(function Panel() {
<ConversationListItem
title={chat.title || 'New Chat'}
isActive={Boolean(chat.activeStreamId)}
titleClassName='text-[13px]'
titleClassName='text-small'
actions={
<div
className={`flex flex-shrink-0 items-center gap-1 ${copilotChatId !== chat.id ? 'opacity-0 transition-opacity group-hover:opacity-100' : ''}`}
@@ -26,9 +26,18 @@ export const chipFieldSurfaceClass = `rounded-lg ${chipFilledSurfaceTokens} tran
*/
export const chipBorderShadowRing =
'shadow-[0_0_0_1px_rgba(28,40,64,0.08),0_1px_3px_0_rgba(28,40,64,0.1)] dark:shadow-[0_0_0_1px_var(--border-1),0_1px_3px_0_rgba(0,0,0,0.3)]'
/** Typography shared by the chip text fields — normal weight, `--text-body`, muted placeholder, no focus outline. */
/**
* Typography shared by the chip text fields — normal weight, `--text-body`, muted
* placeholder, no focus outline.
*
* `[letter-spacing:inherit]` undoes the UA stylesheet, which pins form controls to
* `letter-spacing: normal`. Without it a chip field's text tracks differently from
* the labels around it, and any transparent-field-over-mirror overlay diverges from
* its mirror by the inherited tracking on every character — so the caret drifts
* further from the visible text the longer the value. Matches `Input`/`Textarea`.
*/
export const chipFieldTextClass =
'text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)]'
'text-[var(--text-body)] text-sm [letter-spacing:inherit] outline-none placeholder:text-[var(--text-muted)]'
/**
* Icon↔label gap of the canonical chip-content row — the icon↔label pair inside
+9 -1
View File
@@ -20,8 +20,16 @@
import * as React from 'react'
import { cn } from '../../lib/cn'
/**
* `[letter-spacing:inherit]` undoes the UA stylesheet, which resets form controls
* to `letter-spacing: normal`. Two reasons it matters: input text otherwise tracks
* differently from every label beside it, and a transparent-input-over-mirror
* overlay (the sub-block editors) diverges from its mirror by the inherited
* tracking on every character — so the caret drifts further from the text the
* longer the value. Keep this in step with `Textarea`.
*/
const INPUT_CLASS =
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50 scroll-pr-1'
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-1.5 font-sans text-sm text-[var(--text-primary)] [letter-spacing:inherit] transition-colors placeholder:text-[var(--text-muted)] outline-none disabled:cursor-not-allowed disabled:opacity-50 scroll-pr-1'
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
@@ -2,8 +2,9 @@ import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '../../lib/cn'
/** `[letter-spacing:inherit]` — see the note on `INPUT_CLASS`; keep the two in step. */
const textareaVariants = cva(
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-2 font-sans text-sm text-[var(--text-primary)] transition-colors placeholder:text-[var(--text-muted)] outline-none resize-none overflow-auto disabled:cursor-not-allowed disabled:opacity-50',
'flex w-full touch-manipulation rounded-sm border border-[var(--border-1)] bg-[var(--surface-5)] px-2 py-2 font-sans text-sm text-[var(--text-primary)] [letter-spacing:inherit] transition-colors placeholder:text-[var(--text-muted)] outline-none resize-none overflow-auto disabled:cursor-not-allowed disabled:opacity-50',
{
variants: {
variant: {