Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@
*/
--color-amber-50: #fffbeb;
--color-amber-200: #fde68a;
--color-amber-300: #fcd34d;
--color-amber-400: #fbbf24;
--color-amber-500: #f59e0b;
--color-amber-600: #d97706;
Expand All @@ -166,7 +165,6 @@
--color-emerald-400: #34d399;
--color-emerald-500: #10b981;
--color-green-500: #22c55e;
--color-orange-400: #fb923c;
--color-purple-500: #a855f7;
--color-red-50: #fef2f2;
--color-red-300: #fca5a5;
Expand Down Expand Up @@ -235,6 +233,18 @@
--animate-hero-edge-draw: hero-edge-draw 520ms ease-out forwards;
}

/** Runtime collaborator colours must exist even without a matching utility class. */
@theme static {
--color-black: #000000;
--color-white: #ffffff;
--color-amber-300: #fcd34d;
--color-orange-400: #fb923c;
--color-pink-400: #f472b6;
--color-purple-400: #c084fc;
--color-violet-500: #8b5cf6;
--color-cool-gray-500: #6b7280;
}
Comment on lines +236 to +246

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Global Styling Violates Requirements

This adds eight color variables in a new global @theme block. The repository requires styling changes to remain local to components and specifically says to avoid editing globals.css unless absolutely necessary. This requirement must be satisfied before merging by keeping these colors in the relevant component styles or retaining local color values.

Rule Used: Avoid editing the globals.css file unless absolutely necessary. Move style changes to local component files instead. (source)

Learned From
simstudioai/sim#367

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


/**
* One period of the running block's hatch (26px horizontal). Shifting by
* exactly one period is what makes the loop seamless.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function PresenceAvatars({
style={{ zIndex: 0 }}
aria-label={`${overflowCount} more ${overflowCount === 1 ? 'user' : 'users'}`}
>
<AvatarFallback className='border-0 bg-[#404040] font-semibold text-[7px] text-white leading-none'>
<AvatarFallback className='border-0 bg-gray-700 font-semibold text-[7px] text-white leading-none'>
+{overflowCount}
</AvatarFallback>
</Avatar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { Awareness } from 'y-protocols/awareness'
export const CARET_LABEL_HOLD_MS = 2000

/** Fallback caret color when a peer's awareness carries no `color`. */
export const DEFAULT_CARET_COLOR = '#000000'
export const DEFAULT_CARET_COLOR = 'var(--color-black)'

/**
* The active-state class {@link activateCaretLabel} toggles on the caret node to reveal the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ import {
} from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/raw-markdown-snippet'
import { SlashCommand } from '@/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/slash-command/slash-command'

const FileCollaborationCaret = CollaborationCaret.extend({
addProseMirrorPlugins() {
// Older peers need a resolved colour to apply their selection opacity.
// Resolve at editor mount, before the parent captures and publishes user.
const color = this.options.user.color
const token = typeof color === 'string' ? /^var\((--[\w-]+)\)$/.exec(color)?.[1] : undefined
if (token && typeof document !== 'undefined') {
const resolved = getComputedStyle(document.documentElement).getPropertyValue(token).trim()
if (resolved) this.options.user = { ...this.options.user, color: resolved }
}
return this.parent?.() ?? []
},
})

/** Live collaboration binding for the editor. When present, the editor's history
* is Yjs-backed and remote carets/selection render via CollaborationCaret. */
export interface EditorCollaboration {
Expand Down Expand Up @@ -87,15 +101,15 @@ export function createMarkdownEditorExtensions({
// relayed by the socket provider once connected). `render` tags each caret
// with the peer's client id and shows its name label; the selection tint is
// a translucent fill of the peer's identity color.
CollaborationCaret.configure({
FileCollaborationCaret.configure({
provider: { awareness: collaboration.awareness },
user: collaboration.user,
render: renderCaret,
selectionRender: (user) => {
const hex = typeof user.color === 'string' ? user.color : DEFAULT_CARET_COLOR
const color = typeof user.color === 'string' ? user.color : DEFAULT_CARET_COLOR
return {
class: 'collaboration-carets__selection',
style: `background-color: ${withAlpha(hex, 0.2)};`,
style: `background-color: ${withAlpha(color, 0.2)};`,
}
},
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,24 @@ describe('list editing with delayed peer updates', () => {
)
})
})

it('publishes resolved global colours so older peers keep translucent selections', () => {
const doc = new Y.Doc()
const awareness = new Awareness(doc)
const user = { name: 'User', color: 'var(--color-pink-400)' }
const extensions = createMarkdownEditorExtensions({
placeholder: '',
collaboration: { doc, awareness, user },
})
document.documentElement.style.setProperty('--color-pink-400', '#f472b6')
const editor = new Editor({ extensions })
cleanups.push(() => {
editor.destroy()
awareness.destroy()
doc.destroy()
document.documentElement.style.removeProperty('--color-pink-400')
})

expect(awareness.getLocalState()?.user.color).toBe('#f472b6')
expect(user.color).toBe('var(--color-pink-400)')
})
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@
* text without ever shifting the text (or any following text) as the highlight is applied/removed.
*/
.rich-markdown-nodes mark {
background-color: rgba(255, 212, 0, 0.4);
background-color: color-mix(in srgb, var(--color-amber-400) 40%, transparent);
color: inherit;
border-radius: 2px;
padding: 0 0.1em;
Expand Down Expand Up @@ -604,5 +604,5 @@
* its solid fill with a fixed dark ink for exactly this reason. */
.rich-markdown-nodes .rich-find-match-active {
background-color: var(--brand-secondary);
color: #000;
color: var(--color-black);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Tailwind class applied to selected rows / columns / cells. */
export const SELECTION_TINT_BG = 'bg-[rgba(37,99,235,0.06)]'
export const SELECTION_TINT_BG = 'bg-[color-mix(in_srgb,var(--selection)_6%,transparent)]'

/**
* Fill marking every cell matching the active find query. Reuses the app's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ export const DiffControls = memo(function DiffControls() {
width: '2px',
transform: 'skewX(-18.4deg)',
background:
'linear-gradient(to right, var(--border) 50%, color-mix(in srgb, var(--brand-accent) 70%, black) 50%)',
'linear-gradient(to right, var(--border) 50%, color-mix(in srgb, var(--brand-accent) 70%, var(--color-black)) 50%)',
}}
/>
{/* Accept side */}
<button
onClick={handleAccept}
title='Accept changes (⇧⌘⏎)'
className='-ml-2.5 relative flex h-full items-center border border-[rgba(0,0,0,0.15)] bg-[var(--brand-accent)] pr-3 pl-5 text-[var(--text-inverse)] text-small transition-[background-color,border-color,fill,stroke] hover-hover:brightness-110 dark:border-[rgba(255,255,255,0.1)]'
className='-ml-2.5 relative flex h-full items-center border border-black/15 bg-[var(--brand-accent)] pr-3 pl-5 text-[var(--text-inverse)] text-small transition-[background-color,border-color,fill,stroke] hover-hover:brightness-110 dark:border-white/10'
style={{
clipPath: 'polygon(10px 0, 100% 0, 100% 100%, 0 100%)',
borderRadius: '0 4px 4px 0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ function ConnectionsSection({
handleKeyboardActivation(event, () => setExpandedVariables(!expandedVariables))
}
>
<div className='relative flex size-[14px] shrink-0 items-center justify-center overflow-hidden rounded-sm bg-[#8B5CF6]'>
<div className='relative flex size-[14px] shrink-0 items-center justify-center overflow-hidden rounded-sm bg-violet-500'>
<span className='text-[9px] text-white'>V</span>
</div>
<OverflowText
Expand Down Expand Up @@ -483,7 +483,7 @@ function ConnectionsSection({
handleKeyboardActivation(event, () => setExpandedEnvVars(!expandedEnvVars))
}
>
<div className='relative flex size-[14px] shrink-0 items-center justify-center overflow-hidden rounded-sm bg-[#6B7280]'>
<div className='relative flex size-[14px] shrink-0 items-center justify-center overflow-hidden rounded-sm bg-cool-gray-500'>
<span className='text-[9px] text-white'>E</span>
</div>
<OverflowText
Expand Down
14 changes: 12 additions & 2 deletions apps/sim/components/ui/shimmer-text.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
* `--shimmer-rest` to their resting color.
*/
.shimmer {
background-image: linear-gradient(90deg, #4a4a4a 40%, #b0b0b0 50%, #4a4a4a 60%);
background-image: linear-gradient(
90deg,
var(--text-body) 40%,
var(--color-gray-400) 50%,
var(--text-body) 60%
);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
Expand All @@ -17,7 +22,12 @@
}

:global(.dark) .shimmer {
background-image: linear-gradient(90deg, #b9b9b9 40%, #f8f8f8 50%, #b9b9b9 60%);
background-image: linear-gradient(
90deg,
var(--text-body) 40%,
var(--color-gray-50) 50%,
var(--text-body) 60%
);
}

@keyframes shimmer-sweep {
Expand Down
39 changes: 39 additions & 0 deletions apps/sim/lib/api/contracts/knowledge/documents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import { describe, expect, it } from 'vitest'
import { z } from 'zod'
import {
bulkCreateDocumentsBodySchema,
createDocumentBodySchema,
documentDataSchema,
listKnowledgeDocumentsQuerySchema,
parseDocumentTagFiltersParam,
updateDocumentBodySchema,
upsertDocumentBodySchema,
} from '@/lib/api/contracts/knowledge/documents'
import { MAX_DOCUMENT_INDEXED_TEXT_LENGTH } from '@/lib/knowledge/constants'
import { getDocumentIndexingStatus } from '@/lib/knowledge/documents/types'

describe('document processing response compatibility', () => {
Expand Down Expand Up @@ -255,3 +258,39 @@ describe('internal document processingOptions', () => {
})
})
})

describe('document filename and tag bounds', () => {
const base = { fileUrl: 'https://example.com/a.txt', fileSize: 1, mimeType: 'text/plain' }
const atLimit = 'a'.repeat(MAX_DOCUMENT_INDEXED_TEXT_LENGTH)
const overLimit = `${atLimit}a`

it('accepts a filename and tag exactly at the indexed-text limit', () => {
expect(
createDocumentBodySchema.safeParse({ ...base, filename: atLimit, tag1: atLimit }).success
).toBe(true)
})

it('rejects a filename over the limit on create, upsert, and update with a descriptive message', () => {
for (const schema of [
createDocumentBodySchema,
upsertDocumentBodySchema,
updateDocumentBodySchema,
]) {
const result = schema.safeParse({ ...base, filename: overLimit })
expect(result.success).toBe(false)
expect(result.error?.issues[0]?.message).toBe(
`Filename cannot exceed ${MAX_DOCUMENT_INDEXED_TEXT_LENGTH} characters`
)
}
})

it('rejects a tag value over the limit on create and update', () => {
for (const schema of [createDocumentBodySchema, updateDocumentBodySchema]) {
const result = schema.safeParse({ ...base, filename: 'a.txt', tag3: overLimit })
expect(result.success).toBe(false)
expect(result.error?.issues[0]?.message).toBe(
`Tag values cannot exceed ${MAX_DOCUMENT_INDEXED_TEXT_LENGTH} characters`
)
}
})
})
67 changes: 49 additions & 18 deletions apps/sim/lib/api/contracts/knowledge/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
import { privateSecretProvenanceBundleSchema } from '@/lib/api/contracts/primitives'
import { defineRouteContract } from '@/lib/api/contracts/types'
import { PRIVATE_SECRET_PROVENANCE_FIELD } from '@/lib/execution/private-tool-metadata'
import { getFieldTypeForSlot, MAX_KNOWLEDGE_DOCUMENTS_PER_CREATE } from '@/lib/knowledge/constants'
import {
getFieldTypeForSlot,
MAX_DOCUMENT_INDEXED_TEXT_LENGTH,
MAX_KNOWLEDGE_DOCUMENTS_PER_CREATE,
} from '@/lib/knowledge/constants'
import { DOCUMENT_PROCESSING_STATUSES } from '@/lib/knowledge/documents/types'
import { getOperatorsForFieldType, isValidFilterValue } from '@/lib/knowledge/filters/types'
import { knowledgeDocumentUploadMetadataSchema } from '@/lib/knowledge/upload-metadata'
Expand Down Expand Up @@ -115,18 +119,32 @@ export function parseDocumentTagFiltersParam(
return z.array(documentTagFilterSchema).parse(JSON.parse(value))
}

/** A text tag value that fits its index row; see {@link MAX_DOCUMENT_INDEXED_TEXT_LENGTH}. */
const documentTagValueSchema = z
.string()
.max(
MAX_DOCUMENT_INDEXED_TEXT_LENGTH,
`Tag values cannot exceed ${MAX_DOCUMENT_INDEXED_TEXT_LENGTH} characters`
)

export const createDocumentBodySchema = z.object({
filename: z.string().min(1, 'Filename is required'),
filename: z
.string()
.min(1, 'Filename is required')
.max(
MAX_DOCUMENT_INDEXED_TEXT_LENGTH,
`Filename cannot exceed ${MAX_DOCUMENT_INDEXED_TEXT_LENGTH} characters`
),
fileUrl: knowledgeDocumentFileUrlSchema,
fileSize: z.number().min(1, 'File size must be greater than 0'),
mimeType: z.string().min(1, 'MIME type is required'),
tag1: z.string().optional(),
tag2: z.string().optional(),
tag3: z.string().optional(),
tag4: z.string().optional(),
tag5: z.string().optional(),
tag6: z.string().optional(),
tag7: z.string().optional(),
tag1: documentTagValueSchema.optional(),
tag2: documentTagValueSchema.optional(),
tag3: documentTagValueSchema.optional(),
tag4: documentTagValueSchema.optional(),
tag5: documentTagValueSchema.optional(),
tag6: documentTagValueSchema.optional(),
tag7: documentTagValueSchema.optional(),
documentTagsData: z.string().optional(),
})

Expand Down Expand Up @@ -165,7 +183,13 @@ export type SingleCreateDocumentBody = z.input<typeof singleCreateDocumentBodySc

export const upsertDocumentBodySchema = z.object({
documentId: z.string().optional(),
filename: z.string().min(1, 'Filename is required'),
filename: z
.string()
.min(1, 'Filename is required')
.max(
MAX_DOCUMENT_INDEXED_TEXT_LENGTH,
`Filename cannot exceed ${MAX_DOCUMENT_INDEXED_TEXT_LENGTH} characters`
),
fileUrl: knowledgeDocumentFileUrlSchema,
fileSize: z.number().min(1, 'File size must be greater than 0'),
mimeType: z.string().min(1, 'MIME type is required'),
Expand Down Expand Up @@ -196,7 +220,14 @@ export const bulkCreateDocumentsResponseSchema = z.object({
})

export const updateDocumentBodySchema = z.object({
filename: z.string().min(1, 'Filename is required').optional(),
filename: z
.string()
.min(1, 'Filename is required')
.max(
MAX_DOCUMENT_INDEXED_TEXT_LENGTH,
`Filename cannot exceed ${MAX_DOCUMENT_INDEXED_TEXT_LENGTH} characters`
)
.optional(),
enabled: z.boolean().optional(),
chunkCount: z.number().min(0).optional(),
tokenCount: z.number().min(0).optional(),
Expand All @@ -205,13 +236,13 @@ export const updateDocumentBodySchema = z.object({
processingError: z.string().optional(),
markFailedDueToTimeout: z.boolean().optional(),
retryProcessing: z.boolean().optional(),
tag1: z.string().optional(),
tag2: z.string().optional(),
tag3: z.string().optional(),
tag4: z.string().optional(),
tag5: z.string().optional(),
tag6: z.string().optional(),
tag7: z.string().optional(),
tag1: documentTagValueSchema.optional(),
tag2: documentTagValueSchema.optional(),
tag3: documentTagValueSchema.optional(),
tag4: documentTagValueSchema.optional(),
tag5: documentTagValueSchema.optional(),
tag6: documentTagValueSchema.optional(),
tag7: documentTagValueSchema.optional(),
number1: z.string().optional(),
number2: z.string().optional(),
number3: z.string().optional(),
Expand Down
6 changes: 4 additions & 2 deletions apps/sim/lib/knowledge/application/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
toKnowledgeTagFilterConditions,
} from '@/lib/knowledge/tags/filter-resolution'
import { getDocumentTagDefinitions } from '@/lib/knowledge/tags/service'
import { validateTagValue } from '@/lib/knowledge/tags/utils'
import { validateTagValue, validateTagValueLength } from '@/lib/knowledge/tags/utils'
import { StorageService } from '@/lib/uploads'
import { generateKnowledgeBaseFileKey } from '@/lib/uploads/contexts/knowledge-base/knowledge-base-file-manager'
import { recordKnowledgeBaseFileOwnership } from '@/lib/uploads/server/metadata'
Expand Down Expand Up @@ -252,7 +252,9 @@ async function resolveKnowledgeDocumentTagValueUpdates(
`Tag "${definition.displayName}" requires a value; use null to clear it`
)
}
const validationError = validateTagValue(definition.displayName, value, definition.fieldType)
const validationError =
validateTagValueLength(definition.displayName, value) ??
validateTagValue(definition.displayName, value, definition.fieldType)
if (validationError) {
throw new OrchestrationError('validation', validationError)
}
Expand Down
Loading
Loading