diff --git a/client/src/components/Chat/Messages/Content/ProgressCircle.tsx b/client/src/components/Chat/Messages/Content/ProgressCircle.tsx
deleted file mode 100644
index be4cd3be151..00000000000
--- a/client/src/components/Chat/Messages/Content/ProgressCircle.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-export default function ProgressCircle({
- radius,
- circumference,
- offset,
-}: {
- radius: number;
- circumference: number;
- offset: number;
-}) {
- return (
-
- );
-}
diff --git a/client/src/components/Chat/Messages/Content/RetrievalIcon.tsx b/client/src/components/Chat/Messages/Content/RetrievalIcon.tsx
deleted file mode 100644
index fd9dfec07d9..00000000000
--- a/client/src/components/Chat/Messages/Content/RetrievalIcon.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-export default function RetrievalIcon() {
- return (
-
- );
-}
diff --git a/client/src/components/Chat/Messages/Content/WrenchIcon.tsx b/client/src/components/Chat/Messages/Content/WrenchIcon.tsx
deleted file mode 100644
index 9343f04b404..00000000000
--- a/client/src/components/Chat/Messages/Content/WrenchIcon.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, { useState, useEffect } from 'react';
-
-export default function WrenchIcon() {
- const [rotate, setRotate] = useState(false);
-
- useEffect(() => {
- const timer = setInterval(() => {
- setRotate((r) => !r);
- }, 2000); // Change 2000 to the duration you want for each pause
-
- return () => clearInterval(timer);
- }, []);
-
- return (
-
- );
-}
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index 27d7fcfa3a8..8b6f65acc6a 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -215,7 +215,7 @@
},
"client/src/components/Chat/Input/Files/DragDropOverlay.tsx": {
"shadcn/no-inline-styles": {
- "count": 4
+ "count": 3
},
"shadcn/no-raw-colors": {
"count": 5
@@ -408,14 +408,6 @@
"count": 4
}
},
- "client/src/components/Chat/Messages/Content/ActionIcon.tsx": {
- "shadcn/no-inline-styles": {
- "count": 13
- },
- "shadcn/no-raw-colors": {
- "count": 9
- }
- },
"client/src/components/Chat/Messages/Content/ActivityPhaseGroup.tsx": {
"shadcn/no-arbitrary-values": {
"count": 1
@@ -660,11 +652,6 @@
"count": 6
}
},
- "client/src/components/Chat/Messages/Content/ProgressCircle.tsx": {
- "shadcn/no-arbitrary-values": {
- "count": 1
- }
- },
"client/src/components/Chat/Messages/Content/ProgressText.tsx": {
"shadcn/no-inline-styles": {
"count": 2
@@ -681,14 +668,6 @@
"count": 1
}
},
- "client/src/components/Chat/Messages/Content/RetrievalIcon.tsx": {
- "shadcn/no-inline-styles": {
- "count": 5
- },
- "shadcn/no-raw-colors": {
- "count": 7
- }
- },
"client/src/components/Chat/Messages/Content/SearchContent.tsx": {
"shadcn/no-unknown-classes": {
"count": 1
@@ -768,14 +747,6 @@
"count": 16
}
},
- "client/src/components/Chat/Messages/Content/WrenchIcon.tsx": {
- "shadcn/no-inline-styles": {
- "count": 1
- },
- "shadcn/no-raw-colors": {
- "count": 1
- }
- },
"client/src/components/Chat/Messages/Content/animate.tsx": {
"shadcn/no-inline-styles": {
"count": 1
diff --git a/packages/client/src/theme/allowlist.md b/packages/client/src/theme/allowlist.md
index 5d7c48398ec..41f393af275 100644
--- a/packages/client/src/theme/allowlist.md
+++ b/packages/client/src/theme/allowlist.md
@@ -39,3 +39,15 @@ is a role, not an exception.
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `packages/client/src/components/Dropdown.css` | Popover shadows in black alpha. The app paints `.popover-ui` from its own copy of these rules in `client/src/style.css`, so moving either copy to `--theme-shadow-lg` needs both edited together. |
| `packages/client/src/components/Tooltip.css` | Tooltip shadows are smaller than any step of the theme shadow scale; black alpha ink, dropped in high contrast where the border takes over. |
+
+## Chat: media, artwork and documents that leave the app
+
+| File | Why |
+| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `client/src/components/Chat/Input/Files/DragDropOverlay.tsx` | The drop-zone illustration's fills are artwork; the backdrop behind it is the theme scrim. |
+| `client/src/components/Chat/Input/Files/SourceIcon.tsx` | The code-execution badge is a black terminal mark with white ink, an identity like the provider marks. |
+| `client/src/components/Chat/Input/Files/ProgressCircle.tsx` | Upload progress drawn over the user's own image: a media scrim, so it stays black and white whatever the theme paints around the image. |
+| `client/src/utils/markdown.ts` | The downloaded HTML document's GitHub Primer stylesheet; it is read outside the app, where no theme variables exist. Its high-contrast branch already reads the roles. |
+| `client/src/utils/mermaid.ts` | The mermaid artifact document and its zoom controls render in a sandboxed iframe without theme variables; the token reads' fallbacks and the luminance repair ink are literals by necessity. |
+| `client/src/utils/richtext.ts` | Inline styles on HTML copied to the clipboard for pasting into other apps, which must not carry the sender's theme. |
+| `client/src/utils/officePreview.ts`, `client/src/hooks/ScreenshotContext.tsx` | Fallbacks for a theme role read at runtime, used only when the variable is unset. |
From c23f99db33f95dec7bedc5ed8a86c2c4e2f44f78 Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Tue, 29 Sep 2026 08:21:03 +0200
Subject: [PATCH 2/5] fix: Keep the Drag-and-Drop Backdrop Black Until Scrim
Opacity Is a Role
The theme scrim is gray in default light, so at the same alpha it dimmed the page
less than the black backdrop it replaced. The backdrop keeps its original colour
and the allowlist records the scrim-opacity role it waits on.
---
.../Chat/Input/Files/DragDropOverlay.tsx | 14 +++++++++-----
eslint-suppressions.json | 2 +-
packages/client/src/theme/allowlist.md | 18 +++++++++---------
3 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/client/src/components/Chat/Input/Files/DragDropOverlay.tsx b/client/src/components/Chat/Input/Files/DragDropOverlay.tsx
index 50d55faccd4..43700206c33 100644
--- a/client/src/components/Chat/Input/Files/DragDropOverlay.tsx
+++ b/client/src/components/Chat/Input/Files/DragDropOverlay.tsx
@@ -11,16 +11,20 @@ const DragDropOverlay = memo(({ isActive }: DragDropOverlayProps) => {
<>
{/** Modal backdrop overlay */}
{/** Main content overlay */}
{
}}
>
{/** Content area with subtle background */}
-
+
{localize('com_ui_upload_files')}
-
{localize('com_ui_drag_drop')}
+
{localize('com_ui_drag_drop')}
>
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index 8b6f65acc6a..5b5892571cc 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -215,7 +215,7 @@
},
"client/src/components/Chat/Input/Files/DragDropOverlay.tsx": {
"shadcn/no-inline-styles": {
- "count": 3
+ "count": 4
},
"shadcn/no-raw-colors": {
"count": 5
diff --git a/packages/client/src/theme/allowlist.md b/packages/client/src/theme/allowlist.md
index 41f393af275..2f340e399ce 100644
--- a/packages/client/src/theme/allowlist.md
+++ b/packages/client/src/theme/allowlist.md
@@ -42,12 +42,12 @@ is a role, not an exception.
## Chat: media, artwork and documents that leave the app
-| File | Why |
-| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `client/src/components/Chat/Input/Files/DragDropOverlay.tsx` | The drop-zone illustration's fills are artwork; the backdrop behind it is the theme scrim. |
-| `client/src/components/Chat/Input/Files/SourceIcon.tsx` | The code-execution badge is a black terminal mark with white ink, an identity like the provider marks. |
-| `client/src/components/Chat/Input/Files/ProgressCircle.tsx` | Upload progress drawn over the user's own image: a media scrim, so it stays black and white whatever the theme paints around the image. |
-| `client/src/utils/markdown.ts` | The downloaded HTML document's GitHub Primer stylesheet; it is read outside the app, where no theme variables exist. Its high-contrast branch already reads the roles. |
-| `client/src/utils/mermaid.ts` | The mermaid artifact document and its zoom controls render in a sandboxed iframe without theme variables; the token reads' fallbacks and the luminance repair ink are literals by necessity. |
-| `client/src/utils/richtext.ts` | Inline styles on HTML copied to the clipboard for pasting into other apps, which must not carry the sender's theme. |
-| `client/src/utils/officePreview.ts`, `client/src/hooks/ScreenshotContext.tsx` | Fallbacks for a theme role read at runtime, used only when the variable is unset. |
+| File | Why |
+| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `client/src/components/Chat/Input/Files/DragDropOverlay.tsx` | The drop-zone illustration's fills are artwork. Its backdrop is black at 40% in both modes; the theme scrim is gray-500 in default light, so it moves to `surface-overlay` once a theme can set scrim opacity per mode (berry-13/LibreChat#181). |
+| `client/src/components/Chat/Input/Files/SourceIcon.tsx` | The code-execution badge is a black terminal mark with white ink, an identity like the provider marks. |
+| `client/src/components/Chat/Input/Files/ProgressCircle.tsx` | Upload progress drawn over the user's own image: a media scrim, so it stays black and white whatever the theme paints around the image. |
+| `client/src/utils/markdown.ts` | The downloaded HTML document's GitHub Primer stylesheet; it is read outside the app, where no theme variables exist. Its high-contrast branch already reads the roles. |
+| `client/src/utils/mermaid.ts` | The mermaid artifact document and its zoom controls render in a sandboxed iframe without theme variables; the token reads' fallbacks and the luminance repair ink are literals by necessity. |
+| `client/src/utils/richtext.ts` | Inline styles on HTML copied to the clipboard for pasting into other apps, which must not carry the sender's theme. |
+| `client/src/utils/officePreview.ts`, `client/src/hooks/ScreenshotContext.tsx` | Fallbacks for a theme role read at runtime, used only when the variable is unset. |
From d0a50a0388deee4cf3a71d5096f16a6b20f2a3cd Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Tue, 29 Sep 2026 08:27:19 +0200
Subject: [PATCH 3/5] test: Cover the Code Window Header Showing Its Panel
Surface
---
.../mock/scenarios/code-window-header.spec.ts | 51 +++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 e2e/specs/mock/scenarios/code-window-header.spec.ts
diff --git a/e2e/specs/mock/scenarios/code-window-header.spec.ts b/e2e/specs/mock/scenarios/code-window-header.spec.ts
new file mode 100644
index 00000000000..15113cf6a0d
--- /dev/null
+++ b/e2e/specs/mock/scenarios/code-window-header.spec.ts
@@ -0,0 +1,51 @@
+import { expect, test } from '@playwright/test';
+import {
+ sendMessageAndWaitForCompletion,
+ enableCodeInterpreter,
+ selectMockEndpoint,
+ MOCK_ENDPOINTS,
+ NEW_CHAT_PATH,
+ messagesView,
+} from '../helpers';
+
+/**
+ * The code window header used to repaint its panel with a per-mode fill that matched the panel
+ * in every bundled theme. It now paints nothing, so the header always shows the panel's own
+ * `surface-secondary`, including in a theme where the two roles differ.
+ */
+test('the code window header shows its panel surface @scenario:code-window-header-shows-its-panel', async ({
+ page,
+}) => {
+ test.setTimeout(120_000);
+ const label = `header-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+ await page.goto(NEW_CHAT_PATH, { timeout: 10_000 });
+ await selectMockEndpoint(page, MOCK_ENDPOINTS[0]);
+ await enableCodeInterpreter(page);
+
+ const response = await sendMessageAndWaitForCompletion(page, `E2E_EXECUTE_CODE:${label}`);
+ expect(response.ok()).toBeTruthy();
+
+ const card = messagesView(page).getByRole('button', { name: /^Finished running/ });
+ await expect(card).toBeVisible({ timeout: 30_000 });
+ await card.click();
+
+ const copy = messagesView(page).getByRole('button', { name: 'Copy code' }).first();
+ await expect(copy).toBeVisible({ timeout: 15_000 });
+
+ const paint = await copy.evaluate((button) => {
+ const header = button.parentElement as HTMLElement;
+ const panel = header.parentElement as HTMLElement;
+ const probe = document.createElement('div');
+ probe.style.backgroundColor = 'rgb(var(--surface-secondary))';
+ document.body.appendChild(probe);
+ const surfaceSecondary = getComputedStyle(probe).backgroundColor;
+ probe.remove();
+ return {
+ header: getComputedStyle(header).backgroundColor,
+ panel: getComputedStyle(panel).backgroundColor,
+ surfaceSecondary,
+ };
+ });
+ expect(paint.header).toBe('rgba(0, 0, 0, 0)');
+ expect(paint.panel).toBe(paint.surfaceSecondary);
+});
From c0bb5611640171801da3748288caffe061806ba6 Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Tue, 29 Sep 2026 08:51:34 +0200
Subject: [PATCH 4/5] test: Exercise the Code Window Header Through a
File-Authoring Card
The mock code interpreter exposes bash_tool, whose card has its own header; a skill
file created by the model opens the file-authoring card that renders CodeWindowHeader.
---
.../mock/scenarios/code-window-header.spec.ts | 80 ++++++++++++-------
1 file changed, 52 insertions(+), 28 deletions(-)
diff --git a/e2e/specs/mock/scenarios/code-window-header.spec.ts b/e2e/specs/mock/scenarios/code-window-header.spec.ts
index 15113cf6a0d..96637abe473 100644
--- a/e2e/specs/mock/scenarios/code-window-header.spec.ts
+++ b/e2e/specs/mock/scenarios/code-window-header.spec.ts
@@ -1,51 +1,75 @@
import { expect, test } from '@playwright/test';
import {
- sendMessageAndWaitForCompletion,
- enableCodeInterpreter,
- selectMockEndpoint,
MOCK_ENDPOINTS,
NEW_CHAT_PATH,
+ enableSkills,
+ fetchJson,
+ getAccessToken,
messagesView,
+ requestJson,
+ selectMockEndpoint,
+ sendMessage,
} from '../helpers';
/**
* The code window header used to repaint its panel with a per-mode fill that matched the panel
* in every bundled theme. It now paints nothing, so the header always shows the panel's own
- * `surface-secondary`, including in a theme where the two roles differ.
+ * `surface-secondary`, including in a theme where the two roles differ. A skill file created by
+ * the model opens a file-authoring card, whose preview carries the header.
*/
+type SkillSummary = { _id: string; name: string };
+
test('the code window header shows its panel surface @scenario:code-window-header-shows-its-panel', async ({
page,
}) => {
test.setTimeout(120_000);
- const label = `header-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+ const skillName = `e2e-code-header-${Date.now()}-${Math.floor(Math.random() * 1e4)}`;
await page.goto(NEW_CHAT_PATH, { timeout: 10_000 });
await selectMockEndpoint(page, MOCK_ENDPOINTS[0]);
- await enableCodeInterpreter(page);
+ await enableSkills(page);
- const response = await sendMessageAndWaitForCompletion(page, `E2E_EXECUTE_CODE:${label}`);
+ const response = await sendMessage(
+ page,
+ [`E2E_CREATE_SKILL:${skillName}`, 'Create the skill file using host file authoring only.'].join(
+ '\n',
+ ),
+ );
expect(response.ok()).toBeTruthy();
- const card = messagesView(page).getByRole('button', { name: /^Finished running/ });
- await expect(card).toBeVisible({ timeout: 30_000 });
- await card.click();
+ try {
+ const card = messagesView(page).getByRole('button', { name: /^Created / });
+ await expect(card).toBeVisible({ timeout: 30_000 });
+ await card.click();
- const copy = messagesView(page).getByRole('button', { name: 'Copy code' }).first();
- await expect(copy).toBeVisible({ timeout: 15_000 });
+ const copy = messagesView(page).getByRole('button', { name: 'Copy code', exact: true }).first();
+ await expect(copy).toBeVisible({ timeout: 15_000 });
- const paint = await copy.evaluate((button) => {
- const header = button.parentElement as HTMLElement;
- const panel = header.parentElement as HTMLElement;
- const probe = document.createElement('div');
- probe.style.backgroundColor = 'rgb(var(--surface-secondary))';
- document.body.appendChild(probe);
- const surfaceSecondary = getComputedStyle(probe).backgroundColor;
- probe.remove();
- return {
- header: getComputedStyle(header).backgroundColor,
- panel: getComputedStyle(panel).backgroundColor,
- surfaceSecondary,
- };
- });
- expect(paint.header).toBe('rgba(0, 0, 0, 0)');
- expect(paint.panel).toBe(paint.surfaceSecondary);
+ const paint = await copy.evaluate((button) => {
+ const header = button.closest('div.justify-between') as HTMLElement;
+ const panel = header.parentElement as HTMLElement;
+ const probe = document.createElement('div');
+ probe.style.backgroundColor = 'rgb(var(--surface-secondary))';
+ document.body.appendChild(probe);
+ const surfaceSecondary = getComputedStyle(probe).backgroundColor;
+ probe.remove();
+ return {
+ header: getComputedStyle(header).backgroundColor,
+ panel: getComputedStyle(panel).backgroundColor,
+ surfaceSecondary,
+ };
+ });
+ expect(paint.header).toBe('rgba(0, 0, 0, 0)');
+ expect(paint.panel).toBe(paint.surfaceSecondary);
+ } finally {
+ const token = await getAccessToken(page);
+ const listed = await fetchJson<{ skills?: SkillSummary[] }>(
+ page,
+ `/api/skills?search=${encodeURIComponent(skillName)}&limit=10`,
+ token,
+ );
+ const skill = listed.skills?.find((item) => item.name === skillName);
+ if (skill) {
+ await requestJson(page, { path: `/api/skills/${skill._id}`, token, method: 'DELETE' });
+ }
+ }
});
From 094ac1bfb098a12ee4a4b7a5d3cdac7dc4972cbc Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Tue, 29 Sep 2026 08:55:56 +0200
Subject: [PATCH 5/5] fix: Keep the OpenAI File-Source Chip's Dark Opacity
The chip composites over the file's own preview, where surface-primary at 75% darkened
it against the shipped black at 65%. It keeps its colours and joins the code-execution
badge in the allowlist as a media overlay.
---
client/src/components/Chat/Input/Files/SourceIcon.tsx | 2 +-
packages/client/src/theme/allowlist.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/src/components/Chat/Input/Files/SourceIcon.tsx b/client/src/components/Chat/Input/Files/SourceIcon.tsx
index 6ff1c062436..63a8ef790b2 100644
--- a/client/src/components/Chat/Input/Files/SourceIcon.tsx
+++ b/client/src/components/Chat/Input/Files/SourceIcon.tsx
@@ -9,7 +9,7 @@ const sourceToEndpoint = {
};
const sourceToClassname = {
- [FileSources.openai]: 'bg-surface-primary/75',
+ [FileSources.openai]: 'bg-white/75 dark:bg-black/65',
[FileSources.azure]: 'azure-bg-color',
[FileSources.azure_blob]: 'azure-bg-color',
[FileSources.execute_code]: 'bg-black text-white opacity-85',
diff --git a/packages/client/src/theme/allowlist.md b/packages/client/src/theme/allowlist.md
index 2f340e399ce..6eaf706be4d 100644
--- a/packages/client/src/theme/allowlist.md
+++ b/packages/client/src/theme/allowlist.md
@@ -45,7 +45,7 @@ is a role, not an exception.
| File | Why |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `client/src/components/Chat/Input/Files/DragDropOverlay.tsx` | The drop-zone illustration's fills are artwork. Its backdrop is black at 40% in both modes; the theme scrim is gray-500 in default light, so it moves to `surface-overlay` once a theme can set scrim opacity per mode (berry-13/LibreChat#181). |
-| `client/src/components/Chat/Input/Files/SourceIcon.tsx` | The code-execution badge is a black terminal mark with white ink, an identity like the provider marks. |
+| `client/src/components/Chat/Input/Files/SourceIcon.tsx` | The OpenAI chip (white/75, black/65 in dark) and the code-execution badge (a black terminal mark with white ink) sit over the file's own preview: media overlays, like the upload progress. |
| `client/src/components/Chat/Input/Files/ProgressCircle.tsx` | Upload progress drawn over the user's own image: a media scrim, so it stays black and white whatever the theme paints around the image. |
| `client/src/utils/markdown.ts` | The downloaded HTML document's GitHub Primer stylesheet; it is read outside the app, where no theme variables exist. Its high-contrast branch already reads the roles. |
| `client/src/utils/mermaid.ts` | The mermaid artifact document and its zoom controls render in a sandboxed iframe without theme variables; the token reads' fallbacks and the luminance repair ink are literals by necessity. |