-
+
+
-
{fileName}
-
+
{fileName}
+
{data?.mimeType ?? localize('com_ui_unknown_file_type')}
diff --git a/e2e/specs/mock/scenarios/category-filter-state.spec.ts b/e2e/specs/mock/scenarios/category-filter-state.spec.ts
new file mode 100644
index 00000000000..f9fa4a96435
--- /dev/null
+++ b/e2e/specs/mock/scenarios/category-filter-state.spec.ts
@@ -0,0 +1,74 @@
+import { expect, test } from '@playwright/test';
+import type { Locator, Page } from '@playwright/test';
+import { openAgentBuilder } from '../agents.helpers';
+import { getAccessToken, requestJson } from '../helpers';
+
+/**
+ * Picking a category in the Skills dialog marks the filter as active on its icon, drawn in the
+ * theme's accent role; the resting filter keeps the tertiary icon. The state used to be a raw
+ * emerald border no theme could set.
+ */
+type SkillSummary = { _id: string; name: string };
+
+async function resolveRole(page: Page, role: string): Promise
{
+ return page.evaluate((name) => {
+ const probe = document.createElement('div');
+ probe.style.color = `rgb(var(--${name}))`;
+ document.body.appendChild(probe);
+ const color = getComputedStyle(probe).color;
+ probe.remove();
+ return color;
+ }, role);
+}
+
+async function openSkillsPicker(page: Page): Promise {
+ const form = await openAgentBuilder(page);
+ await form.getByRole('radio', { name: 'Selected', exact: true }).click();
+ const addSkill = form.getByRole('button', { name: /Add skill/ }).first();
+ await expect(addSkill).toBeVisible();
+ await addSkill.click();
+ const dialog = page.getByRole('dialog', { name: 'Skills', exact: true });
+ await expect(dialog).toBeVisible();
+ return dialog;
+}
+
+const filterIconColor = (trigger: Locator) =>
+ trigger
+ .locator('svg')
+ .first()
+ .evaluate((icon) => getComputedStyle(icon).color);
+
+test('an active skills category shows on the filter icon in the accent role @scenario:skills-category-filter-shows-active-state', async ({
+ page,
+}) => {
+ test.setTimeout(120_000);
+ await page.goto('/c/new', { timeout: 15_000 });
+ const token = await getAccessToken(page);
+ const suffix = `${Date.now().toString(36)}-${Math.floor(Math.random() * 1e4)}`;
+ const category = `theme-${suffix}`;
+ const skill = await requestJson(page, {
+ path: '/api/skills',
+ token,
+ method: 'POST',
+ body: {
+ name: `filter-state-${suffix}`,
+ description: 'Carries a category so the Skills dialog offers a category filter.',
+ body: '# Category filter\n\nUsed by the category filter end-to-end test.',
+ category,
+ },
+ });
+
+ try {
+ const dialog = await openSkillsPicker(page);
+ const trigger = dialog.getByRole('button', { name: 'Category', exact: true });
+ await expect(trigger).toBeVisible();
+ expect(await filterIconColor(trigger)).toBe(await resolveRole(page, 'text-tertiary'));
+
+ await trigger.click();
+ await page.getByRole('menuitem', { name: category }).click();
+ await expect(trigger).toContainText(category);
+ expect(await filterIconColor(trigger)).toBe(await resolveRole(page, 'accent-primary'));
+ } finally {
+ await requestJson(page, { path: `/api/skills/${skill._id}`, token, method: 'DELETE' });
+ }
+});
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index 5b5892571cc..4476c66c82c 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -1730,11 +1730,8 @@
}
},
"client/src/components/SidePanel/Agents/Tools/CategoryFilter.tsx": {
- "shadcn/no-raw-colors": {
- "count": 1
- },
"shadcn/no-restyle": {
- "count": 6
+ "count": 4
}
},
"client/src/components/SidePanel/Agents/Tools/ItemDialog/ItemDialog.tsx": {
@@ -2086,9 +2083,6 @@
"shadcn/no-arbitrary-values": {
"count": 3
},
- "shadcn/no-raw-colors": {
- "count": 1
- },
"shadcn/no-restyle": {
"count": 5
}
diff --git a/packages/client/src/theme/allowlist.md b/packages/client/src/theme/allowlist.md
index 6eaf706be4d..53155448f3d 100644
--- a/packages/client/src/theme/allowlist.md
+++ b/packages/client/src/theme/allowlist.md
@@ -51,3 +51,10 @@ is a role, not an exception.
| `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. |
+
+## Panels: media and third-party surfaces
+
+| File | Why |
+| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
+| `client/src/components/Nav/SettingsTabs/Account/Avatar.tsx` | The move hint drawn over the user's photo in the avatar cropper: a media scrim with white ink, independent of the theme around the image. |
+| `client/src/hooks/Files/useSharePointPicker.ts` | Background of Microsoft's SharePoint file picker iframe, matched to that third party's own surface. |