From 4ae69d214588dadf90ff75ab3d1dfc5ca7e9636f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 11 Jul 2026 10:37:42 -0700 Subject: [PATCH 1/4] fix(rich-markdown-editor): fix mention chip losing ambient color (same class as #5573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auditing the whole "an element's own explicit color always wins over an inherited one" bug class (previously fixed for strong/em/code/del/s vs. links and h6 in #5573) turned up one more instance: the @-mention chip's label hardcoded text-[var(--text-primary)], which is redundant with the prose default anyway (matching the strong/em/code precedent) and silently overrides any ambient color a mention's container legitimately sets — a link's blue, or h6's dimmer --text-secondary — since a mention is inline content that can appear inside either (e.g. "###### see @some-file"). Removed the hardcoded color entirely so the label inherits correctly in every context, same fix as strong/em/code. The icon's own monochrome --text-icon fallback is untouched (icons intentionally don't follow ambient text color). New test renders MentionChipView directly and asserts the wrapper carries no explicit text-color utility class; verified it fails against the pre-fix className. --- .../mention/mention-chip.test.tsx | 67 +++++++++++++++++++ .../mention/mention-chip.tsx | 11 ++- 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx new file mode 100644 index 00000000000..1dbe4a205ee --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx @@ -0,0 +1,67 @@ +/** + * @vitest-environment jsdom + * + * The chip label must never carry its own explicit text color — see the comment on `CHIP_CLASS` in + * `mention-chip.tsx`. An element's own explicit `color` always wins over an inherited one regardless + * of ancestor specificity, so hardcoding a color here would silently override any ambient color a + * mention's container legitimately sets (a link's blue, an `h6` heading's dimmer `--text-secondary`) — + * the same bug class already fixed for `strong`/`em`/`code` in `rich-markdown-editor.css`. + */ +import { act } from 'react' +import type { Editor } from '@tiptap/react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, describe, expect, it, vi } from 'vitest' + +vi.mock('next/navigation', () => ({ + useRouter: () => ({ push: vi.fn() }), + useParams: () => ({}), +})) + +// Override the global `getAllBlocks: () => ({})` stub — `getIconColorMap` iterates it as an array. +vi.mock('@/blocks/registry', () => ({ + getAllBlocks: () => [], +})) + +const { MentionChipView } = await import('./mention-chip') + +function fakeNode(attrs: Record) { + return { attrs } as unknown as Parameters[0]['node'] +} + +function fakeEditor(): Editor { + return { storage: { mention: { navigable: false } } } as unknown as Editor +} + +let container: HTMLDivElement | null = null +let root: Root | null = null + +afterEach(() => { + if (root) act(() => root?.unmount()) + container?.remove() + container = null + root = null +}) + +describe('MentionChipView', () => { + it('renders its wrapper with no explicit text-color utility class', async () => { + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + + await act(async () => { + root?.render( + MentionChipView({ + node: fakeNode({ kind: 'file', id: 'f1', label: 'notes.md' }), + editor: fakeEditor(), + } as Parameters[0]) + ) + }) + + const chip = container.querySelector('.mention-chip') as HTMLElement + expect(chip).not.toBeNull() + expect(chip.className).not.toMatch(/text-\[var\(--text-primary\)\]/) + // The icon's own monochrome fallback is unrelated and must be untouched by this fix. + expect(chip.className).toContain('[&>svg]:text-[var(--text-icon)]') + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx index d64d5e4958e..b851c2a86fa 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx @@ -15,9 +15,16 @@ import { simLinkPath } from './sim-link' * color, and a 12px icon. Integration icons keep their brand color via * {@link getBareIconStyle} (see {@link MentionChipView}); other kinds stay * monochrome through the `--text-icon` fallback below. + * + * No explicit label color — an element's own explicit `color` always wins over an inherited one + * regardless of ancestor specificity, so hardcoding `--text-primary` here (redundant with the prose + * default anyway) would silently override any ambient color a ancestor legitimately sets — a link's + * blue, or `h6`'s dimmer `--text-secondary` — since a mention is inline content and can appear inside + * either. Omitting it lets the label inherit correctly in both cases, same fix as `strong`/`em`/`code` + * in rich-markdown-editor.css. */ const CHIP_CLASS = - 'mention-chip mx-px inline-flex items-center gap-1 align-middle text-[var(--text-primary)] leading-[1.5] [&>svg]:size-[12px] [&>svg]:shrink-0 [&>svg]:text-[var(--text-icon)]' + 'mention-chip mx-px inline-flex items-center gap-1 align-middle leading-[1.5] [&>svg]:size-[12px] [&>svg]:shrink-0 [&>svg]:text-[var(--text-icon)]' /** * Live chip: an entity icon + label matching the chat input's mention rendering. Where the host opted @@ -25,7 +32,7 @@ const CHIP_CLASS = * inert so a click can't navigate away from an unsaved edit. This view pulls the block registry (for * integration brand icons), so it's kept out of the headless {@link MarkdownMention} module. */ -function MentionChipView({ node, editor }: ReactNodeViewProps) { +export function MentionChipView({ node, editor }: ReactNodeViewProps) { const router = useRouter() const params = useParams() const { kind, id, label } = node.attrs as MentionAttrs From 94653620143b167cdf9afc8b1cc139370c4af03f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 11 Jul 2026 10:45:14 -0700 Subject: [PATCH 2/4] fix(rich-markdown-editor): broaden mention-chip color-regression guard beyond the exact old class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile: the test only matched the literal old text-[var(--text-primary)] string — a future edit swapping it for e.g. text-[var(--text-secondary)] or text-blue-500 would still silently reintroduce the ambient-color bug and pass this test. Now checks every non-descendant-scoped (excludes the [&>svg]: icon rule) text-* utility on the wrapper against a color-shaped pattern (arbitrary value, color-shade pairs, or a named color keyword), so any bare text color slipping back in fails. Verified against a text-blue-500 regression. --- .../mention/mention-chip.test.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx index 1dbe4a205ee..20d38ccc110 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx @@ -60,7 +60,20 @@ describe('MentionChipView', () => { const chip = container.querySelector('.mention-chip') as HTMLElement expect(chip).not.toBeNull() - expect(chip.className).not.toMatch(/text-\[var\(--text-primary\)\]/) + + // Any bare (non-descendant-scoped) `text-*` color utility on the wrapper itself would + // regress this fix, not just the specific old `text-[var(--text-primary)]` class — a future + // edit swapping it for e.g. `text-[var(--text-secondary)]` or `text-blue-500` would still + // silently override ambient color and must fail this test too. + const ownColorUtilities = chip.className + .split(/\s+/) + .filter( + (cls) => + !cls.startsWith('[&') && + /^text-(\[.+\]|[a-z]+-\d{2,3}|black|white|current|transparent|inherit)$/.test(cls) + ) + expect(ownColorUtilities).toEqual([]) + // The icon's own monochrome fallback is unrelated and must be untouched by this fix. expect(chip.className).toContain('[&>svg]:text-[var(--text-icon)]') }) From ba4ef59bbbdb504de40aa0af56304e37b9dd549c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 11 Jul 2026 10:53:16 -0700 Subject: [PATCH 3/4] fix(rich-markdown-editor): close the semantic-Tailwind-color gap in the mention-chip test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile: the color-shaped regex still missed semantic theme tokens (text-primary, text-muted-foreground, text-chart-1, etc.) since they don't match a shade-suffix or bracket pattern. Rather than keep enumerating Tailwind's color-naming schemes, flag ANY unscoped text-* utility on the wrapper — none is legitimate on this chip today, so this can only be a color slipping back in. Verified against text-primary/text-muted-foreground/text-chart-1 regressions. --- .../mention/mention-chip.test.tsx | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx index 20d38ccc110..98cd84dd3b5 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx @@ -61,18 +61,17 @@ describe('MentionChipView', () => { const chip = container.querySelector('.mention-chip') as HTMLElement expect(chip).not.toBeNull() - // Any bare (non-descendant-scoped) `text-*` color utility on the wrapper itself would - // regress this fix, not just the specific old `text-[var(--text-primary)]` class — a future - // edit swapping it for e.g. `text-[var(--text-secondary)]` or `text-blue-500` would still - // silently override ambient color and must fail this test too. - const ownColorUtilities = chip.className + // Any bare (non-descendant-scoped) `text-*` utility on the wrapper itself would regress this + // fix — not just the specific old `text-[var(--text-primary)]` class. Rather than enumerate + // every Tailwind color-naming scheme (arbitrary value, shade-suffixed, semantic theme tokens + // like `text-primary`/`text-muted-foreground`, keywords), flag ANY unscoped `text-*` token: + // none is legitimate on this wrapper today, so this can only ever be a color utility slipping + // back in. A genuinely new, non-color `text-*` need (e.g. a font-size utility) should fail this + // test and force an explicit update, not be silently allowed through. + const ownTextUtilities = chip.className .split(/\s+/) - .filter( - (cls) => - !cls.startsWith('[&') && - /^text-(\[.+\]|[a-z]+-\d{2,3}|black|white|current|transparent|inherit)$/.test(cls) - ) - expect(ownColorUtilities).toEqual([]) + .filter((cls) => !cls.startsWith('[&') && cls.startsWith('text-')) + expect(ownTextUtilities).toEqual([]) // The icon's own monochrome fallback is unrelated and must be untouched by this fix. expect(chip.className).toContain('[&>svg]:text-[var(--text-icon)]') From fa40a6db70bf29c3fbb873d6efffa7734a0b59b6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 11 Jul 2026 10:57:11 -0700 Subject: [PATCH 4/4] fix(rich-markdown-editor): catch Tailwind's self-targeting [&]:text-* variant too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile: the previous filter excluded ANY class starting with `[&`, which also dropped Tailwind's self-targeting arbitrary variant (`[&]:text-primary` applies to the element itself, same as a bare `text-primary`) — only descendant variants like `[&>svg]:text-*` should be excluded. Now explicitly catches both the bare and `[&]:` forms. Verified against a `[&]:text-primary` regression. --- .../mention/mention-chip.test.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx index 98cd84dd3b5..e91226d8757 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx @@ -61,16 +61,18 @@ describe('MentionChipView', () => { const chip = container.querySelector('.mention-chip') as HTMLElement expect(chip).not.toBeNull() - // Any bare (non-descendant-scoped) `text-*` utility on the wrapper itself would regress this - // fix — not just the specific old `text-[var(--text-primary)]` class. Rather than enumerate - // every Tailwind color-naming scheme (arbitrary value, shade-suffixed, semantic theme tokens - // like `text-primary`/`text-muted-foreground`, keywords), flag ANY unscoped `text-*` token: - // none is legitimate on this wrapper today, so this can only ever be a color utility slipping - // back in. A genuinely new, non-color `text-*` need (e.g. a font-size utility) should fail this - // test and force an explicit update, not be silently allowed through. + // Any `text-*` utility targeting the wrapper itself — bare, or Tailwind's self-targeting + // `[&]:text-*` arbitrary variant (as opposed to a descendant variant like `[&>svg]:text-*`, + // which the icon rule below legitimately uses) — would regress this fix, not just the specific + // old `text-[var(--text-primary)]` class. Rather than enumerate every Tailwind color-naming + // scheme (arbitrary value, shade-suffixed, semantic theme tokens like `text-primary`/ + // `text-muted-foreground`, keywords), flag ANY such token: none is legitimate on this wrapper + // today, so this can only ever be a color utility slipping back in. A genuinely new, non-color + // `text-*` need (e.g. a font-size utility) should fail this test and force an explicit update, + // not be silently allowed through. const ownTextUtilities = chip.className .split(/\s+/) - .filter((cls) => !cls.startsWith('[&') && cls.startsWith('text-')) + .filter((cls) => cls.startsWith('text-') || cls.startsWith('[&]:text-')) expect(ownTextUtilities).toEqual([]) // The icon's own monochrome fallback is unrelated and must be untouched by this fix.