From c238d3f18e1dcdfd5866f5f007c9f5d8deec0c4c Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Wed, 2 Sep 2026 19:04:46 +0530 Subject: [PATCH 1/2] feat: add lint to block block non-semantic & neutral text classes --- eslint-plugin-rill/index.js | 7 ++ .../no-disallowed-tailwind-text-colors.js | 73 +++++++++++++++++++ eslint.config.js | 10 ++- .../alerts/history/NoAlertRunsYet.svelte | 2 +- .../dashboards/DashboardErrored.svelte | 2 +- .../settings/LogoSettings.svelte | 2 +- .../table/groups/GroupCompositeCell.svelte | 4 +- .../projects/RedeployProjectCTA.svelte | 6 +- .../environment-variables/ActivityCell.svelte | 2 +- .../status/resource-table/NameCell.svelte | 2 +- .../status/resource-table/RefreshCell.svelte | 2 +- .../GeneralAccessSelectorDropdown.svelte | 4 +- .../history/NoRunsYet.svelte | 2 +- .../src/components/CellInspector.svelte | 4 +- .../src/components/avatar/Avatar.svelte | 4 +- .../components/avatar/AvatarListItem.svelte | 2 +- .../src/components/button/Button.svelte | 8 ++ web-common/src/components/button/classes.ts | 2 +- .../TimestampTooltipContent.svelte | 2 +- .../dialog/tabs/NumberedCircle.svelte | 2 +- .../src/components/icons/SendIcon.svelte | 2 +- .../menu/DashboardMetricsDraggableList.svelte | 2 +- .../TableToolbarFilterDropdown.svelte | 2 +- .../components/tooltip/LongDescription.svelte | 2 +- .../src/components/tooltip/Shortcut.svelte | 2 +- .../tooltip/TooltipShortcutContainer.svelte | 2 +- .../virtualized-table/VirtualTableRow.svelte | 2 +- .../src/features/alerts/PreviewEmpty.svelte | 2 +- .../alerts/data-tab/NoFiltersSelected.svelte | 2 +- .../canvas/inspector/AIGenerateButton.svelte | 2 +- .../chat/core/context/InlineContext.svelte | 2 +- .../core/context/picker/SimpleOption.svelte | 2 +- .../inspector/WithModelResultTooltip.svelte | 4 +- .../MergeConflictResolutionDialog.svelte | 4 +- .../project/deploy/DeployError.svelte | 4 +- .../projects/status/RefreshCell.svelte | 4 +- .../status/ResourceErrorMessage.svelte | 2 +- .../graph-canvas/ResourceNode.svelte | 2 +- .../shared/errors/ErrorBoundary.svelte | 2 +- .../src/features/templates/SchemaField.svelte | 2 +- .../features/workspaces/VisualMetrics.svelte | 2 +- .../layout/BlockingOverlayContainer.svelte | 4 +- web-common/tailwind.config.ts | 9 +++ .../src/routes/(misc)/deploy/+page.svelte | 2 +- 44 files changed, 155 insertions(+), 52 deletions(-) create mode 100644 eslint-plugin-rill/index.js create mode 100644 eslint-plugin-rill/no-disallowed-tailwind-text-colors.js diff --git a/eslint-plugin-rill/index.js b/eslint-plugin-rill/index.js new file mode 100644 index 000000000000..1e29adf13495 --- /dev/null +++ b/eslint-plugin-rill/index.js @@ -0,0 +1,7 @@ +import noDisallowedTailwindTextColors from "./no-disallowed-tailwind-text-colors.js"; + +export default { + rules: { + "no-disallowed-tailwind-text-colors": noDisallowedTailwindTextColors, + }, +}; diff --git a/eslint-plugin-rill/no-disallowed-tailwind-text-colors.js b/eslint-plugin-rill/no-disallowed-tailwind-text-colors.js new file mode 100644 index 000000000000..18585677fa7d --- /dev/null +++ b/eslint-plugin-rill/no-disallowed-tailwind-text-colors.js @@ -0,0 +1,73 @@ +/** + * ESLint rule to disallow certain Tailwind text color classes. + * Disallows: text-gray-*, text-neutral-*, text-slate-*, text-stone-*, text-zinc-* + */ + +const DISALLOWED_PATTERN = /\btext-(gray|neutral|slate|stone|zinc)-\d{1,3}\b/g; +const ERROR_MESSAGE = + 'Disallowed Tailwind text color class: "{{ className }}". Use semantic color classes instead.'; + +function reportAllMatches(value, context, node) { + if (typeof value !== "string") return; + + for (const match of value.matchAll(DISALLOWED_PATTERN)) { + context.report({ + node, + message: ERROR_MESSAGE, + data: { className: match[0] }, + }); + } +} + +export default { + meta: { + type: "problem", + docs: { + description: + "Disallow non-semantic Tailwind text color classes (gray, neutral, slate, stone, zinc)", + }, + schema: [], + }, + create(context) { + const sourceCode = context.sourceCode ?? context.getSourceCode(); + + return { + // Check Svelte HTML attributes (class="..." and className="...") + SvelteAttribute(node) { + if (node.key?.name === "class" || node.key?.name === "className") { + for (const valueNode of node.value) { + if (valueNode.type === "SvelteLiteral") { + reportAllMatches(valueNode.value, context, valueNode); + } + } + } + }, + // Check Svelte shorthand class directives (class:text-gray-500) + SvelteDirective(node) { + if (node.kind === "Class" && node.key?.name) { + const className = node.key.name.name || node.key.name; + reportAllMatches(className, context, node); + } + }, + // Check Svelte diff --git a/web-common/src/features/alerts/PreviewEmpty.svelte b/web-common/src/features/alerts/PreviewEmpty.svelte index 62aeda311de2..44383411e797 100644 --- a/web-common/src/features/alerts/PreviewEmpty.svelte +++ b/web-common/src/features/alerts/PreviewEmpty.svelte @@ -6,7 +6,7 @@
- +
{topLine}
diff --git a/web-common/src/features/alerts/data-tab/NoFiltersSelected.svelte b/web-common/src/features/alerts/data-tab/NoFiltersSelected.svelte index c10266bafa40..78750bd6c968 100644 --- a/web-common/src/features/alerts/data-tab/NoFiltersSelected.svelte +++ b/web-common/src/features/alerts/data-tab/NoFiltersSelected.svelte @@ -6,7 +6,7 @@
- +

{m.alert_no_filters_heading()} diff --git a/web-common/src/features/canvas/inspector/AIGenerateButton.svelte b/web-common/src/features/canvas/inspector/AIGenerateButton.svelte index 9e9995821917..3f83581b059c 100644 --- a/web-common/src/features/canvas/inspector/AIGenerateButton.svelte +++ b/web-common/src/features/canvas/inspector/AIGenerateButton.svelte @@ -113,6 +113,6 @@ } .status { - @apply text-xs text-gray-500 py-1; + @apply text-xs text-fg-muted py-1; } diff --git a/web-common/src/features/chat/core/context/InlineContext.svelte b/web-common/src/features/chat/core/context/InlineContext.svelte index ec278eef8956..1757854a30f4 100644 --- a/web-common/src/features/chat/core/context/InlineContext.svelte +++ b/web-common/src/features/chat/core/context/InlineContext.svelte @@ -94,7 +94,7 @@ {/snippet} - + {tooltip} diff --git a/web-common/src/features/chat/core/context/picker/SimpleOption.svelte b/web-common/src/features/chat/core/context/picker/SimpleOption.svelte index ebf6a5d5477f..5d39fc8f2cfa 100644 --- a/web-common/src/features/chat/core/context/picker/SimpleOption.svelte +++ b/web-common/src/features/chat/core/context/picker/SimpleOption.svelte @@ -43,7 +43,7 @@ {/if}

{#if icon} -
+
{:else} diff --git a/web-common/src/features/models/inspector/WithModelResultTooltip.svelte b/web-common/src/features/models/inspector/WithModelResultTooltip.svelte index d7cc00cd499e..45417e9cdcfc 100644 --- a/web-common/src/features/models/inspector/WithModelResultTooltip.svelte +++ b/web-common/src/features/models/inspector/WithModelResultTooltip.svelte @@ -19,11 +19,11 @@
-

+

{#if modelHasError} -

+

-

+

What are merge conflicts?

-
+
Conflicts occur when same part of the file has been changed in different ways. You need to choose which version to keep.
diff --git a/web-common/src/features/project/deploy/DeployError.svelte b/web-common/src/features/project/deploy/DeployError.svelte index 53c1247ec1f8..dba13395f3fb 100644 --- a/web-common/src/features/project/deploy/DeployError.svelte +++ b/web-common/src/features/project/deploy/DeployError.svelte @@ -33,14 +33,14 @@ {:else if isGithubNoAccessError} - + {deployError.title} {deployError.message} Retry connection {:else} - + {deployError.title} {deployError.message} {#if deployError.type === DeployErrorType.Unknown} diff --git a/web-common/src/features/projects/status/RefreshCell.svelte b/web-common/src/features/projects/status/RefreshCell.svelte index dda2fd082db8..90704eadd0dc 100644 --- a/web-common/src/features/projects/status/RefreshCell.svelte +++ b/web-common/src/features/projects/status/RefreshCell.svelte @@ -33,11 +33,11 @@ {formattedDate}
- + {full} {:else} - - + - {/if} diff --git a/web-common/src/features/projects/status/ResourceErrorMessage.svelte b/web-common/src/features/projects/status/ResourceErrorMessage.svelte index 36c01aee5315..8e481b8c7a7f 100644 --- a/web-common/src/features/projects/status/ResourceErrorMessage.svelte +++ b/web-common/src/features/projects/status/ResourceErrorMessage.svelte @@ -56,7 +56,7 @@ > diff --git a/web-common/src/features/resource-graph/graph-canvas/ResourceNode.svelte b/web-common/src/features/resource-graph/graph-canvas/ResourceNode.svelte index d70b1b3e7990..177528d62e41 100644 --- a/web-common/src/features/resource-graph/graph-canvas/ResourceNode.svelte +++ b/web-common/src/features/resource-graph/graph-canvas/ResourceNode.svelte @@ -317,7 +317,7 @@ .toolbar-open-btn { @apply h-7 px-3 rounded-[2px] border flex items-center justify-center gap-x-1.5 shadow-sm transition-colors; @apply text-xs font-medium; - @apply bg-primary-600 text-white border-primary-600; + @apply bg-primary-600 text-fg-primary border-primary-600; } .toolbar-open-btn:focus { diff --git a/web-common/src/features/resource-graph/shared/errors/ErrorBoundary.svelte b/web-common/src/features/resource-graph/shared/errors/ErrorBoundary.svelte index 0842adadf59c..97e8f43d6aba 100644 --- a/web-common/src/features/resource-graph/shared/errors/ErrorBoundary.svelte +++ b/web-common/src/features/resource-graph/shared/errors/ErrorBoundary.svelte @@ -174,7 +174,7 @@ } .btn-primary { - @apply rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white; + @apply rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-fg-primary; } .btn-primary:hover { diff --git a/web-common/src/features/templates/SchemaField.svelte b/web-common/src/features/templates/SchemaField.svelte index 6e2c1f00fc9d..753ceda25fd4 100644 --- a/web-common/src/features/templates/SchemaField.svelte +++ b/web-common/src/features/templates/SchemaField.svelte @@ -63,7 +63,7 @@
{prop.title ?? id} {#if prop.description} - {prop.description} + {prop.description} {/if}
diff --git a/web-common/src/features/workspaces/VisualMetrics.svelte b/web-common/src/features/workspaces/VisualMetrics.svelte index a1d34a0ff98b..efe8e523b8bb 100644 --- a/web-common/src/features/workspaces/VisualMetrics.svelte +++ b/web-common/src/features/workspaces/VisualMetrics.svelte @@ -763,7 +763,7 @@ {#if totalSelected}
{m.visual_metrics_items_selected({ count: totalSelected })} diff --git a/web-common/src/layout/BlockingOverlayContainer.svelte b/web-common/src/layout/BlockingOverlayContainer.svelte index 1aa386526369..8c9d476ede0c 100644 --- a/web-common/src/layout/BlockingOverlayContainer.svelte +++ b/web-common/src/layout/BlockingOverlayContainer.svelte @@ -23,12 +23,12 @@
diff --git a/web-common/tailwind.config.ts b/web-common/tailwind.config.ts index 5c585a316e7c..3e753427f8ee 100644 --- a/web-common/tailwind.config.ts +++ b/web-common/tailwind.config.ts @@ -4,6 +4,14 @@ import { TailwindColors, } from "./src/features/themes/color-config"; +const blockedTextColors = ["gray", "neutral", "slate", "stone", "zinc"]; + +const TEXT_BLOCKLIST = blockedTextColors + .map((color) => { + return TailwindColorSpacing.map((spacing) => `text-${color}-${spacing}`); + }) + .flat(); + function generateTailwindVariables() { const colors: Record> = {}; @@ -159,4 +167,5 @@ export default { }, }, }, + // blocklist: TEXT_BLOCKLIST, } satisfies Config; diff --git a/web-local/src/routes/(misc)/deploy/+page.svelte b/web-local/src/routes/(misc)/deploy/+page.svelte index a10d7d79b9f8..9ec5c8e880c4 100644 --- a/web-local/src/routes/(misc)/deploy/+page.svelte +++ b/web-local/src/routes/(misc)/deploy/+page.svelte @@ -113,7 +113,7 @@ {:else if error} - + Oops! An error occurred {error.message} {/if} From 13e511ee8c4ca9caf36ea6d01f36d64b1b2a4773 Mon Sep 17 00:00:00 2001 From: Aditya Hegde Date: Thu, 3 Sep 2026 16:48:02 +0530 Subject: [PATCH 2/2] Add background to the blocklist --- eslint-plugin-rill/index.js | 2 +- .../no-disallowed-tailwind-colors.js | 91 +++++++++++++++++++ .../no-disallowed-tailwind-text-colors.js | 73 --------------- .../billing/plans/EnterprisePlan.svelte | 2 +- .../custom-chart/AgenticChartPrompt.svelte | 2 +- .../canvas/inspector/AIGenerateButton.svelte | 4 +- .../inspector/chart/MetricsSQLInput.svelte | 6 +- .../errors/InlineErrorIndicator.svelte | 2 +- 8 files changed, 100 insertions(+), 82 deletions(-) create mode 100644 eslint-plugin-rill/no-disallowed-tailwind-colors.js delete mode 100644 eslint-plugin-rill/no-disallowed-tailwind-text-colors.js diff --git a/eslint-plugin-rill/index.js b/eslint-plugin-rill/index.js index 1e29adf13495..2185896e5140 100644 --- a/eslint-plugin-rill/index.js +++ b/eslint-plugin-rill/index.js @@ -1,4 +1,4 @@ -import noDisallowedTailwindTextColors from "./no-disallowed-tailwind-text-colors.js"; +import noDisallowedTailwindTextColors from "./no-disallowed-tailwind-colors.js"; export default { rules: { diff --git a/eslint-plugin-rill/no-disallowed-tailwind-colors.js b/eslint-plugin-rill/no-disallowed-tailwind-colors.js new file mode 100644 index 000000000000..5de8386262ed --- /dev/null +++ b/eslint-plugin-rill/no-disallowed-tailwind-colors.js @@ -0,0 +1,91 @@ +/** + * ESLint rule to disallow certain Tailwind text color classes. + * Disallows: text-gray-*, text-neutral-*, text-slate-*, text-stone-*, text-zinc-* + */ + +const DISALLOWED_TEXT_PATTERN = + /\btext-(gray|neutral|slate|stone|zinc)-\d{1,3}\b/g; +const TEXT_ERROR_MESSAGE = + 'Disallowed Tailwind text color class: "{{ className }}". Use semantic color classes instead.'; + +const DISALLOWED_BACKGROUND_PATTERN = + /\bbg-(gray|neutral|slate|stone|zinc)-\d{1,3}\b/g; +const DISALLOWED_BACKGROUND_CLASSES = /\bbg-(white|black)\b/g; +const BACKGROUND_ERROR_MESSAGE = + 'Disallowed Tailwind background color class: "{{ className }}". Use semantic color classes instead.'; + +const DISALLOWED_CLASSES_PATTERNS = [ + [DISALLOWED_TEXT_PATTERN, TEXT_ERROR_MESSAGE], + + [DISALLOWED_BACKGROUND_PATTERN, BACKGROUND_ERROR_MESSAGE], + [DISALLOWED_BACKGROUND_CLASSES, BACKGROUND_ERROR_MESSAGE], +]; + +function reportAllMatches(value, context, node) { + if (typeof value !== "string") return; + + for (const [pattern, errorMessage] of DISALLOWED_CLASSES_PATTERNS) { + for (const match of value.matchAll(pattern)) { + context.report({ + node, + message: errorMessage, + data: { className: match[0] }, + }); + } + } +} + +export default { + meta: { + type: "problem", + docs: { + description: + "Disallow non-semantic Tailwind text/background color classes (gray, neutral, slate, stone, zinc)", + }, + schema: [], + }, + create(context) { + const sourceCode = context.sourceCode ?? context.getSourceCode(); + + return { + // Check Svelte HTML attributes (class="..." and className="...") + SvelteAttribute(node) { + if (node.key?.name === "class" || node.key?.name === "className") { + for (const valueNode of node.value) { + if (valueNode.type === "SvelteLiteral") { + reportAllMatches(valueNode.value, context, valueNode); + } + } + } + }, + // Check Svelte shorthand class directives (class:text-gray-500) + SvelteDirective(node) { + if (node.kind === "Class" && node.key?.name) { + const className = node.key.name.name || node.key.name; + reportAllMatches(className, context, node); + } + }, + // Check Svelte