Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: block disallowed text classes
  • Loading branch information
briangregoryholmes committed Jan 27, 2026
commit 1fb91f68ebe59fc6a3166ef4af6275ccaf782c94
7 changes: 7 additions & 0 deletions eslint-plugin-rill/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import noDisallowedTailwindTextColors from "./no-disallowed-tailwind-text-colors.js";

export default {
rules: {
"no-disallowed-tailwind-text-colors": noDisallowedTailwindTextColors,
},
};
81 changes: 81 additions & 0 deletions eslint-plugin-rill/no-disallowed-tailwind-text-colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* 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 string literals in JS/TS
Literal(node) {
reportAllMatches(node.value, context, node);
},
// Check template literals
TemplateElement(node) {
reportAllMatches(node.value.raw, context, node);
},
// Check Svelte HTML attributes (class="...")
SvelteAttribute(node) {
if (node.key?.name === "class") {
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 <style> blocks
SvelteStyleElement(node) {
const styleText = sourceCode.getText(node);
const nodeStart = node.range[0];

for (const match of styleText.matchAll(DISALLOWED_PATTERN)) {
const matchStart = nodeStart + match.index;
const matchEnd = matchStart + match[0].length;

context.report({
loc: {
start: sourceCode.getLocFromIndex(matchStart),
end: sourceCode.getLocFromIndex(matchEnd),
},
message: ERROR_MESSAGE,
data: { className: match[0] },
});
}
},
};
},
};
6 changes: 6 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import eslintPluginSvelte from "eslint-plugin-svelte";
import globals from "globals";
import tsEslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";
import rillPlugin from "./eslint-plugin-rill/index.js";

export default [
js.configs.recommended,
Expand All @@ -20,6 +21,9 @@ export default [
},
...eslintPluginSvelte.configs["flat/prettier"],
{
plugins: {
rill: rillPlugin,
},
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
Expand All @@ -32,6 +36,7 @@ export default [
},
},
rules: {
"rill/no-disallowed-tailwind-text-colors": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
Expand Down Expand Up @@ -68,6 +73,7 @@ export default [
"**/playwright.config.js",
"**/postcss.config.cjs",
"**/svelte.config.js",
"eslint-plugin-rill/*",
"web-admin/build/*",
"web-admin/playwright-report/*",
"web-admin/playwright/*",
Expand Down
20 changes: 20 additions & 0 deletions scripts/web-test-code-quality.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ echo "== NPM Install =="
# https://typicode.github.io/husky/how-to.html#ci-server-and-docker
HUSKY=0 npm install

echo ""
echo "== Check for disallowed Tailwind text color classes =="
# Always check all web directories - this is fast and enforces consistent color usage
disallowed_matches=$(grep -rn --include='*.svelte' --include='*.ts' --include='*.js' \
--exclude-dir='.svelte-kit' --exclude-dir='node_modules' --exclude-dir='build' \
-E 'text-(gray|neutral|slate|stone|zone)-[0-9]+' \
web-admin web-common web-local 2>/dev/null || true)

if [[ -n "$disallowed_matches" ]]; then
echo "ERROR: Found disallowed Tailwind text color classes."
echo "Use semantic color classes instead of: text-gray-*, text-neutral-*, text-slate-*, text-stone-*, text-zone-*"
echo ""
echo "$disallowed_matches"
if [[ "$FAIL_FAST" == "true" ]]; then
exit 1
else
exit_code=1
fi
fi

if [[ "$COMMON" == "true" ]]; then
echo ""
echo "== lint and type checks for web common =="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
>
<div class="flex flex-col justify-center items-center">
<div class="relative">
<AlertCircleOutline className="text-gray-300 w-12 h-12" />
<AlertCircleOutline className="text-icon-muted w-12 h-12" />
</div>
</div>
<div
Expand Down
2 changes: 1 addition & 1 deletion web-admin/src/features/dashboards/DashboardErrored.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</script>

<div class="flex flex-col justify-center items-center h-3/5 space-y-6 m-auto">
<CancelCircleInverse size="7em" className="text-gray-200" />
<CancelCircleInverse size="7em" className="text-icon-muted" />
<div class="flex flex-col items-center space-y-2">
<h1 class="text-lg font-semibold">
Sorry, your dashboard isn't working right now!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
{#if $wakingProjects}
<div class="flex flex-col justify-center items-center gap-y-6 my-20">
<div class="flex flex-col gap-y-2">
<LoadingCircleOutline size="104px" className="text-gray-300" />
<LoadingCircleOutline size="104px" className="text-icon-muted" />
<CTAHeader>Hang tight! We're waking up your projects...</CTAHeader>
<CTANeedHelp />
</div>
Expand All @@ -68,7 +68,7 @@
<svelte:component
this={IconMap[issueForHibernation.iconType]}
size="104px"
className="text-gray-300"
className="text-icon-muted"
gradientStopColor="slate-200"
/>
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<div class="flex flex-col justify-center items-center gap-y-6 my-20">
<div class="flex flex-col gap-y-2">
<MoonCircleOutline size="104px" className="text-gray-300" />
<MoonCircleOutline size="104px" className="text-icon-muted" />
<CTAHeader variant="bold">This org’s projects are hibernating</CTAHeader>
<CTAMessage>
Please reach out to your administrator to regain access.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@
getRandomBgColor(name),
)}
>
<span class="text-sm text-white font-semibold">{getInitials(name)}</span>
<span class="text-sm text-fg-primary font-semibold"
>{getInitials(name)}</span
>
</div>
<div class="flex flex-col text-left">
<span class="text-sm font-medium text-fg-primary flex flex-row gap-x-1">
Expand All @@ -114,7 +116,7 @@
</div>
<TooltipContent slot="tooltip-content">
{#if (usersCount ?? 0) === 0}
<div class="text-xs text-gray-300 px-1 py-0.5">No users</div>
<div class="text-xs text-fg-muted px-1 py-0.5">No users</div>
{:else if $listUsergroupMemberUsers.isLoading}
<div class="px-1 py-0.5">
<Spinner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<TimeAgo datetime={updatedOn} />
</div>
<TooltipContent slot="tooltip-content">
<span class="text-xs text-gray-50 font-medium">
<span class="text-xs font-medium">
{fullDate}
</span>
</TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
{error.message}
</span>
{#if error.filePath}
<span class="text-stone-500 font-semibold shrink-0">
<span class="text-fg-muted font-semibold shrink-0">
{error.filePath}
</span>
{/if}
Expand Down
2 changes: 1 addition & 1 deletion web-admin/src/features/projects/status/RefreshCell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
{formattedDate}
</div>
<TooltipContent slot="tooltip-content">
<span class="text-xs text-gray-50 font-medium">
<span class="text-xs font-medium">
{full}
</span>
</TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
getRandomBgColor(`Everyone at ${organization}`),
)}
>
<span class="text-sm text-white font-semibold"
<span class="text-sm text-fg-primary font-semibold"
>{getInitials(`Everyone at ${organization}`)}</span
>
</div>
Expand Down Expand Up @@ -220,7 +220,7 @@
<div
class="h-5 w-5 flex items-center justify-center bg-primary-600 rounded-sm"
>
<span class="text-xs text-white font-semibold"
<span class="text-xs text-fg-primary font-semibold"
>{organization[0].toUpperCase()}</span
>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
>
<div class="flex flex-col justify-center items-center">
<div class="relative">
<ReportIcon className="text-gray-300 w-12 h-12" />
<ReportIcon className="text-icon-muted w-12 h-12" />
</div>
</div>
<div
Expand Down
4 changes: 2 additions & 2 deletions web-common/src/components/avatar/Avatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@
<Avatar.Image {src} {alt} />
{#if alt}
<!-- Show a fallback if the image fails to load -->
<Avatar.Fallback class={cn(fontSize, "text-white")}>
<Avatar.Fallback class={cn(fontSize, "text-fg-primary")}>
{getInitials(alt ?? "")}
</Avatar.Fallback>
{/if}
{:else if alt}
<Avatar.Fallback class={cn(fontSize, "text-white")}>
<Avatar.Fallback class={cn(fontSize, "text-fg-primary")}>
{getInitials(alt)}
</Avatar.Fallback>
{:else}
Expand Down
4 changes: 3 additions & 1 deletion web-common/src/components/avatar/AvatarListItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
getRandomBgColor(email ?? name),
)}
>
<span class="text-sm text-white font-semibold">{getInitials(name)}</span>
<span class="text-sm text-fg-primary font-semibold"
>{getInitials(name)}</span
>
</div>
{/if}
<div class="flex flex-col text-left">
Expand Down
8 changes: 8 additions & 0 deletions web-common/src/components/button/Button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@
@apply text-fg-muted p-0;
}

.text:focus {
@apply shadow-none;
}

.text:hover {
@apply text-primary-700;
}
Expand Down Expand Up @@ -289,6 +293,10 @@
@apply gap-x-1.5;
}

.toolbar:focus {
@apply shadow-none;
}

.toolbar:hover:not(:disabled) {
@apply bg-gray-600/15;
}
Expand Down
2 changes: 1 addition & 1 deletion web-common/src/components/button/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const disabledClasses = `disabled:cursor-not-allowed disabled:text-fg-pri

export const levels = {
info: {
primary: `bg-gray-800 text-white border rounded-sm border-gray-800 hover:bg-gray-700 hover:border-gray-700 focus:ring-primary-300`,
primary: `bg-gray-800 text-fg-primary border rounded-sm border-gray-800 hover:bg-gray-700 hover:border-gray-700 focus:ring-primary-300`,
secondary:
"text-fg-primary border rounded-sm border-gray-300 shadow-sm hover:bg-surface-hover hover:text-fg-primary hover:border-gray-300 focus:ring-primary-300",
highlighted:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
{#if zooming}<span>Zoomed</span>{:else}<span>Zooming</span>{/if}
to {formatInteger(zoomedRows)} row{#if zoomedRows !== 1}s{/if}
</div>
<div class="text-right text-gray-300 font-normal not-italic">
<div class="text-right text-fg-inverse font-normal not-italic">
{formatBigNumberPercentage(zoomedRows / totalRows)}
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions web-common/src/components/date-picker/Day.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
>
<div
class="day {overlapClass}"
class:text-fg-secondary={outOfMonth}
class:text-fg-disabled={outOfMonth}
class:potential={!!potentialInterval}
class:anchor={areSameDay(anchorDay, date)}
>
Expand All @@ -108,11 +108,11 @@
}

.day:disabled {
@apply pointer-events-none text-gray-300;
@apply pointer-events-none text-fg-disabled;
}

button:hover .day:not(.potential) {
@apply bg-primary-300 text-white;
@apply bg-primary-300 text-fg-primary;
}

button:hover .day:not(.in-range, .start, .end) {
Expand All @@ -123,15 +123,15 @@
.start.potential,
.end.potential,
.full-interval.potential {
@apply bg-gray-200 text-fg-primary;
@apply bg-surface-hover text-fg-primary;
}

.in-range,
.start,
.end,
.full-interval,
.anchor.potential {
@apply bg-primary-400 text-white;
@apply bg-primary-400 text-fg-primary;
}

.end,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</script>

<div
class="w-[18px] h-[18px] text-white rounded-full inline-flex items-center justify-center {bgColor}"
class="w-[18px] h-[18px] text-fg-primary rounded-full inline-flex items-center justify-center {bgColor}"
>
{number}
</div>
2 changes: 1 addition & 1 deletion web-common/src/components/forms/Checkbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
)}
>
<CheckboxPrimitive.Indicator
class={cn("flex items-center justify-center text-white")}
class={cn("flex items-center justify-center text-fg-primary")}
>
<Check class="h-3.5 w-3.5" />
</CheckboxPrimitive.Indicator>
Expand Down
Loading
Loading