diff --git a/ui/litellm-dashboard/eslint.config.mjs b/ui/litellm-dashboard/eslint.config.mjs
index 5bb7cc29792..ad2923bf2a0 100644
--- a/ui/litellm-dashboard/eslint.config.mjs
+++ b/ui/litellm-dashboard/eslint.config.mjs
@@ -99,6 +99,11 @@ const eslintConfig = [
],
rules: { "local/no-ad-hoc-z-index": ["error", { allowPopupLayer: true }] },
},
+ {
+ files: ["src/components/view_logs/TraceView/**/*.tsx", "src/components/lens/**/*.tsx"],
+ ignores: ["src/**/*.test.tsx"],
+ rules: { "local/no-arbitrary-design-value": "error" },
+ },
{
files: ["tests/eslint-rules/**/*.{ts,tsx}"],
rules: { "local/no-noop-hover-variant": "off", "local/no-ad-hoc-z-index": "off" },
diff --git a/ui/litellm-dashboard/scripts/eslint-rules/index.mjs b/ui/litellm-dashboard/scripts/eslint-rules/index.mjs
index 750b8df4e27..3988a810fca 100644
--- a/ui/litellm-dashboard/scripts/eslint-rules/index.mjs
+++ b/ui/litellm-dashboard/scripts/eslint-rules/index.mjs
@@ -4,6 +4,7 @@ import noComplexJsxArrow from "./no-complex-jsx-arrow.mjs";
import filenamePascalCase from "./filename-pascal-case.mjs";
import noNoopHoverVariant from "./no-noop-hover-variant.mjs";
import noAdHocZIndex from "./no-ad-hoc-z-index.mjs";
+import noArbitraryDesignValue from "./no-arbitrary-design-value.mjs";
const plugin = {
rules: {
@@ -13,6 +14,7 @@ const plugin = {
"filename-pascal-case": filenamePascalCase,
"no-noop-hover-variant": noNoopHoverVariant,
"no-ad-hoc-z-index": noAdHocZIndex,
+ "no-arbitrary-design-value": noArbitraryDesignValue,
},
};
diff --git a/ui/litellm-dashboard/scripts/eslint-rules/no-ad-hoc-z-index.mjs b/ui/litellm-dashboard/scripts/eslint-rules/no-ad-hoc-z-index.mjs
index 6af86d2c501..d349539c5a3 100644
--- a/ui/litellm-dashboard/scripts/eslint-rules/no-ad-hoc-z-index.mjs
+++ b/ui/litellm-dashboard/scripts/eslint-rules/no-ad-hoc-z-index.mjs
@@ -1,26 +1,7 @@
+import { utilityOf } from "./tailwind-utility.mjs";
+
const AD_HOC_Z = /^-?z-(?:\d+|\[[^\]]*\]|\([^)]*\))$/;
-const OPENERS = { "[": "]", "(": ")" };
-
-const utilityOf = (token) => {
- const closers = [];
- const lastTopLevelColon = [...token].reduce((found, ch, i) => {
- if (closers.length > 0 && ch === closers[closers.length - 1]) {
- closers.pop();
- return found;
- }
- if (ch in OPENERS) {
- closers.push(OPENERS[ch]);
- return found;
- }
- return ch === ":" && closers.length === 0 ? i : found;
- }, -1);
- return token
- .slice(lastTopLevelColon + 1)
- .replace(/^!/, "")
- .replace(/!$/, "");
-};
-
const classify = (token, allowPopupLayer) => {
const utility = utilityOf(token);
if (AD_HOC_Z.test(utility)) return "adHoc";
diff --git a/ui/litellm-dashboard/scripts/eslint-rules/no-arbitrary-design-value.mjs b/ui/litellm-dashboard/scripts/eslint-rules/no-arbitrary-design-value.mjs
new file mode 100644
index 00000000000..421a5e15cfc
--- /dev/null
+++ b/ui/litellm-dashboard/scripts/eslint-rules/no-arbitrary-design-value.mjs
@@ -0,0 +1,42 @@
+import { utilityOf } from "./tailwind-utility.mjs";
+
+const ARBITRARY_SCALE = /^(?:text|tracking|leading|rounded(?:-[a-z]+)?|border(?:-[a-z]+)?)-\[/;
+const ARBITRARY_PROPERTY = /^\[[a-z-]+:/;
+
+const isOffending = (token) => {
+ const utility = utilityOf(token);
+ return ARBITRARY_SCALE.test(utility) || ARBITRARY_PROPERTY.test(utility);
+};
+
+const rule = {
+ meta: {
+ type: "problem",
+ docs: {
+ description:
+ "Disallow arbitrary font size, tracking, leading, radius and border values, and arbitrary CSS properties. Use the theme scale so surfaces share one type and shape system.",
+ },
+ schema: [],
+ messages: {
+ arbitrary:
+ "`{{token}}` bypasses the theme scale. Use a scale utility (text-xs/sm, leading-*, tracking-*, rounded-sm/md/lg, border/border-2) instead.",
+ },
+ },
+ create(context) {
+ const check = (node, value) => {
+ if (typeof value !== "string" || !value.includes("[")) return;
+ for (const token of value.split(/\s+/).filter(isOffending)) {
+ context.report({ node, messageId: "arbitrary", data: { token } });
+ }
+ };
+ return {
+ Literal(node) {
+ check(node, node.value);
+ },
+ TemplateElement(node) {
+ check(node, node.value.cooked);
+ },
+ };
+ },
+};
+
+export default rule;
diff --git a/ui/litellm-dashboard/scripts/eslint-rules/tailwind-utility.mjs b/ui/litellm-dashboard/scripts/eslint-rules/tailwind-utility.mjs
new file mode 100644
index 00000000000..69c80a51d5e
--- /dev/null
+++ b/ui/litellm-dashboard/scripts/eslint-rules/tailwind-utility.mjs
@@ -0,0 +1,20 @@
+const OPENERS = { "[": "]", "(": ")" };
+
+export const utilityOf = (token) => {
+ const closers = [];
+ const lastTopLevelColon = [...token].reduce((found, ch, i) => {
+ if (closers.length > 0 && ch === closers[closers.length - 1]) {
+ closers.pop();
+ return found;
+ }
+ if (ch in OPENERS) {
+ closers.push(OPENERS[ch]);
+ return found;
+ }
+ return ch === ":" && closers.length === 0 ? i : found;
+ }, -1);
+ return token
+ .slice(lastTopLevelColon + 1)
+ .replace(/^!/, "")
+ .replace(/!$/, "");
+};
diff --git a/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx b/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx
index 6f400bd638d..00340963e93 100644
--- a/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx
+++ b/ui/litellm-dashboard/src/components/lens/investigations/InvestigationList.tsx
@@ -26,16 +26,16 @@ import { scopeLabel } from "../model/format";
import { type Finding, type Lens } from "../model/types";
import { useListSearchRoute } from "../route";
-const PRIORITY_COLOR = { high: "text-[#e5484d]", medium: "text-amber-500", low: "text-muted-foreground" } as const;
+const PRIORITY_COLOR = { high: "text-destructive", medium: "text-amber-500", low: "text-muted-foreground" } as const;
const ROW =
"border-b border-border/60 transition-colors duration-150 hover:bg-trace-row-hover motion-reduce:transition-none";
-const META = "truncate text-[11px] text-muted-foreground";
+const META = "truncate text-xs text-muted-foreground";
function JobIcon({ lens }: { lens: Lens }) {
const status = lens.jobs[0]?.status;
const className = "size-4 shrink-0";
if (status === "queued" || status === "running")
- return ;
+ return ;
if (status === "failed") return ;
if (status === "cancelled")
return ;
@@ -47,7 +47,7 @@ function TwoLine({ meta, title, className }: { meta: ReactNode; title: ReactNode
return (
{meta}
- {title}
+ {title}
);
}
@@ -87,7 +87,7 @@ export function InvestigationList({
setSearch(e.target.value)}
/>
@@ -149,7 +149,7 @@ export function InvestigationList({
@@ -159,14 +159,14 @@ export function InvestigationList({
{findings.length > 0 && (
{findings.length}
)}
|
{latest ? agoLabel(Date.parse(latest.created_at), now) : "never run"}
@@ -227,12 +227,12 @@ export function InvestigationList({
/>
|
-
+ |
{runs} {runs === 1 ? "run" : "runs"}
|
|
{agoLabel(Date.parse(finding.last_seen), now)}
@@ -249,12 +249,10 @@ export function InvestigationList({
{!shown.length && (
-
- No investigations match your search.
-
+ No investigations match your search.
)}
- |