Repository: windmill-labs/windmill
Pull request #5817: Fix(frontend): auto completion and render of tailwind classes in app editor
Author: login: Guilhem-lm
Created: 2025-05-26T17:47:25Z | Merged: 2025-05-26T23:09:55Z

* Fixes bad rendering of tailwind classes by adding them to the safelist
* Fixes tailwind editor autocompletion not replacing the word correctly, and improve tailwind class detection
<!-- ELLIPSIS_HIDDEN -->

----

> [!IMPORTANT]
> Fixes Tailwind class rendering and improves autocompletion in the app editor by updating `SimpleEditor.svelte` and `tailwind.config.cjs`.
> 
>   - **Behavior**:
>     - Fixes Tailwind class rendering by adding `tailwindClasses` to the safelist in `tailwind.config.cjs`.
>     - Improves Tailwind class autocompletion in `SimpleEditor.svelte` by adjusting word detection and completion range.
>   - **Editor Enhancements**:
>     - Sets custom word definition for Tailwind classes in `addTailwindClassCompletions()` in `SimpleEditor.svelte`.
>     - Updates completion range logic to handle spaces correctly in `SimpleEditor.svelte`.
>   - **Misc**:
>     - Adds `triggerCharacters` for Tailwind class completion in `SimpleEditor.svelte`.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=windmill-labs%2Fwindmill&utm_source=github&utm_medium=referral)<sup> for a39c6a1da516d78fa77b406245ff238a23453b49. You can [customize](https://app.ellipsis.dev/windmill-labs/settings/summaries) this summary. It will automatically update as commits are pushed.</sup>

<!-- ELLIPSIS_HIDDEN -->


Diff:
diff --git a/frontend/package.json b/frontend/package.json
index 51c7820491fe9..f302f5c8735e3 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -371,6 +371,10 @@
 			"types": "./package/components/SimpleEditor.svelte.d.ts",
 			"svelte": "./package/components/SimpleEditor.svelte",
 			"default": "./package/components/SimpleEditor.svelte"
+		},
+		"./tailwindUtils": {
+			"types": "./package/components/apps/editor/componentsPanel/tailwindUtils.d.ts",
+			"default": "./package/components/apps/editor/componentsPanel/tailwindUtils.js"
 		}
 	},
 	"files": [
@@ -506,6 +510,9 @@
 			],
 			"components/icons/store": [
 				"./package/components/icons/store.d.ts"
+			],
+			"tailwindUtils": [
+				"./package/components/apps/editor/componentsPanel/tailwindUtils.d.ts"
 			]
 		}
 	},
diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte
index 3cc814b85550c..984b05564a977 100644
--- a/frontend/src/lib/components/SimpleEditor.svelte
+++ b/frontend/src/lib/components/SimpleEditor.svelte
@@ -467,33 +467,46 @@
 	}
 
 	function addTailwindClassCompletions() {
+		// Define a custom word definition for Tailwind classes
+		languages.setMonarchTokensProvider('tailwindcss', {
+			tokenizer: {
+				root: [[/[a-zA-Z0-9-]+/, 'tailwind-class']]
+			}
+		})
+
 		languages.registerCompletionItemProvider('tailwindcss', {
+			triggerCharacters: ['-'],
 			provideCompletionItems: function (model, position, context, token) {
-				const word = model.getWordUntilPosition(position)
+				const wordUntilPosition = model.getWordUntilPosition(position)
+				const lineContent = model.getLineContent(position.lineNumber)
+
+				// Get the text from the start of the line to the cursor
+				const textUntilPosition = lineContent.substring(0, position.column - 1)
+				// Find the last space before the cursor
+				const lastSpaceIndex = textUntilPosition.lastIndexOf(' ')
+				const startColumn = lastSpaceIndex === -1 ? 1 : lastSpaceIndex + 2
+
 				const range = {
 					startLineNumber: position.lineNumber,
-					startColumn: word.startColumn,
+					startColumn: startColumn,
 					endLineNumber: position.lineNumber,
-					endColumn: word.endColumn
+					endColumn: position.column
 				}
 
-				if (word && word.word) {
-					const currentWord = word.word
-
-					const suggestions = tailwindClasses
-						.filter((className) => className.includes(currentWord))
-						.map((className) => ({
-							label: className,
-							kind: languages.CompletionItemKind.Class,
-							insertText: className,
-							documentation: 'Custom CSS class',
-							range: range
-						}))
+				const currentWord = wordUntilPosition.word
 
-					return { suggestions }
-				}
+				const suggestions = tailwindClasses
+					.filter((className) => className.includes(currentWord))
+					.map((className) => ({
+						label: className,
+						kind: languages.CompletionItemKind.Class,
+						insertText: className,
+						documentation: 'Tailwind CSS class',
+						range: range,
+						preselect: true
+					}))
 
-				return { suggestions: [] }
+				return { suggestions }
 			}
 		})
 	}
diff --git a/frontend/src/lib/components/apps/editor/AppEditor.svelte b/frontend/src/lib/components/apps/editor/AppEditor.svelte
index 3276befa58157..fd328beb2859f 100644
--- a/frontend/src/lib/components/apps/editor/AppEditor.svelte
+++ b/frontend/src/lib/components/apps/editor/AppEditor.svelte
@@ -786,7 +786,6 @@
 </script>
 
 <svelte:head>
-	<link rel="stylesheet" href="/tailwind_full.css" />
 </svelte:head>
 
 <DarkModeObserver on:change={onThemeChange} />
diff --git a/frontend/src/lib/components/apps/editor/AppPreview.svelte b/frontend/src/lib/components/apps/editor/AppPreview.svelte
index 2cc07c05bcbb8..1438f0712c7b1 100644
--- a/frontend/src/lib/components/apps/editor/AppPreview.svelte
+++ b/frontend/src/lib/components/apps/editor/AppPreview.svelte
@@ -243,7 +243,6 @@
 </script>
 
 <svelte:head>
-	<link rel="stylesheet" href="/tailwind_full.css" />
 </svelte:head>
 
 <DarkModeObserver on:change={onThemeChange} />
diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs
index f65a85294991b..908f2516dfa2a 100644
--- a/frontend/tailwind.config.cjs
+++ b/frontend/tailwind.config.cjs
@@ -1,4 +1,5 @@
 const plugin = require('tailwindcss/plugin')
+const { tailwindClasses } = require('./src/lib/components/apps/editor/componentsPanel/tailwindUtils')
 
 const lightTheme = {
 	surface: '#ffffff',
@@ -80,7 +81,8 @@ const config = {
 		'autocomplete-list-item',
 		'autocomplete-list-item-create',
 		'selected',
-		'wm-tab-selected'
+		'wm-tab-selected',
+		...tailwindClasses
 	],
 	theme: {
 		colors: {


Reviews:
user: login: ellipsis-dev[bot]
body: > [!CAUTION]
> **Changes requested  :x:**
> <details>
> <summary>Reviewed everything up to a39c6a1da516d78fa77b406245ff238a23453b49 in <ellipsistime>1 minute and 59 seconds</ellipsistime>. Click for details.</summary> 
> 
> - Reviewed `89` lines of code in `2` files
> - Skipped `0` files when reviewing.
> - Skipped posting `2` draft comments. View those below.
> - Modify your [settings](https://app.ellipsis.dev/windmill-labs/settings/code-review) and [rules](https://app.ellipsis.dev/windmill-labs/code-review/rules) to customize what types of comments Ellipsis leaves. And don't forget to react with :+1: or :-1: to teach Ellipsis.
> 
> <details>
> <summary>1. <code>frontend/src/lib/components/SimpleEditor.svelte:484</code></summary>
> 
> - **Draft comment:** 
> Review the startColumn calculation (using lastIndexOf(' ') + 2). It may not correctly cover cases where a Tailwind class follows punctuation. Consider using a more robust delimiter or boundary detection.
> - **Reason this comment was not posted:**
> Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50% The comment raises a valid point - the code assumes classes are only separated by spaces, but Tailwind classes could appear after other characters like brackets, quotes, etc. However, this is a new feature being added and the current implementation is a reasonable first version. The comment is speculative ("may not correctly cover") rather than pointing out a definite issue. The comment identifies a real edge case that could be problematic. The current implementation could miss valid completion opportunities. While technically correct, this is a new feature addition and the current approach handles the common case. The comment is speculative and doesn't demonstrate an actual problem. Delete the comment. While it raises a valid potential edge case, it's speculative and the current implementation is a reasonable first version of this new feature.
> 
> 
> </details>
> 
> <details>
> <summary>2. <code>frontend/tailwind.config.cjs:85</code></summary>
> 
> - **Draft comment:** 
> Good job including the imported tailwindClasses in the safelist. Ensure that tailwindClasses is always an array to avoid potential runtime errors during Tailwind’s build.
> - **Reason this comment was not posted:**
> Confidence changes required: `33%` <= threshold `50%` None
> 
> 
> </details>
> 
> 
> Workflow ID: <workflowid>`wflow_u78d2rTqc3ioCci6`</workflowid>
> </details>
> 
> <sup>You can customize </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=windmill-labs%2Fwindmill&utm_source=github&utm_medium=referral)<sup> by changing your [verbosity settings](https://app.ellipsis.dev/windmill-labs/settings/code-review), reacting with :+1: or :-1:, [replying to comments](https://docs.ellipsis.dev/features/code-review#teaching-ellipsis), or adding [code review rules](https://app.ellipsis.dev/windmill-labs/code-review/rules).</sup>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T17:49:28Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T23:06:16Z

Review comments:
diff_hunk: @@ -467,33 +467,46 @@
 	}
 
 	function addTailwindClassCompletions() {
+		// Define a custom word definition for Tailwind classes
+		languages.setMonarchTokensProvider('tailwindcss', {
+			tokenizer: {
+				root: [[/[a-zA-Z0-9-]+/, 'tailwind-class']]
path: frontend/src/lib/components/SimpleEditor.svelte
user: login: ellipsis-dev[bot]
body: Consider updating the Monarch token regex to include additional characters (like `:`) for Tailwind variant syntax (e.g. `md:text-center`).
```suggestion
				root: [[/[a-zA-Z0-9:-]+/, 'tailwind-class']]
```

created_at: 2025-05-26T17:49:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -467,33 +467,46 @@
 	}
 
 	function addTailwindClassCompletions() {
+		// Define a custom word definition for Tailwind classes
+		languages.setMonarchTokensProvider('tailwindcss', {
+			tokenizer: {
+				root: [[/[a-zA-Z0-9-]+/, 'tailwind-class']]
+			}
+		})
+
 		languages.registerCompletionItemProvider('tailwindcss', {
+			triggerCharacters: ['-'],
 			provideCompletionItems: function (model, position, context, token) {
-				const word = model.getWordUntilPosition(position)
+				const wordUntilPosition = model.getWordUntilPosition(position)
+				const lineContent = model.getLineContent(position.lineNumber)
+
+				// Get the text from the start of the line to the cursor
+				const textUntilPosition = lineContent.substring(0, position.column - 1)
+				// Find the last space before the cursor
+				const lastSpaceIndex = textUntilPosition.lastIndexOf(' ')
+				const startColumn = lastSpaceIndex === -1 ? 1 : lastSpaceIndex + 2
path: frontend/src/lib/components/SimpleEditor.svelte
user: login: graphite-app[bot]
body: The calculation `lastSpaceIndex + 2` appears to position the cursor two characters after a space, which may skip a character. Consider using `lastSpaceIndex + 1` instead to position the cursor immediately after the space. This would ensure the completion range starts at the correct position for the current word being typed.
```suggestion
				const startColumn = lastSpaceIndex === -1 ? 1 : lastSpaceIndex + 1
```
  

*Spotted by [Diamond](https://app.graphite.dev/diamond/?org=windmill-labs&ref=ai-review-comment)*<i class='graphite__hidden'><br /><br />Is this helpful? React 👍 or 👎 to let us know.</i>
created_at: 2025-05-26T23:06:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -467,33 +467,46 @@
 	}
 
 	function addTailwindClassCompletions() {
+		// Define a custom word definition for Tailwind classes
+		languages.setMonarchTokensProvider('tailwindcss', {
+			tokenizer: {
+				root: [[/[a-zA-Z0-9-]+/, 'tailwind-class']]
path: frontend/src/lib/components/SimpleEditor.svelte
user: login: graphite-app[bot]
body: The regex pattern `[a-zA-Z0-9-]+` doesn't account for all valid Tailwind class characters. Tailwind classes commonly include colons for variants (e.g., `hover:`, `focus:`) and slashes for fractional values (e.g., `w-1/2`). 

Consider updating the pattern to:
```
/[a-zA-Z0-9-:\/]+/
```

This would ensure proper recognition of the full range of Tailwind class syntax during autocompletion.
```suggestion
				root: [[/[a-zA-Z0-9-:\/]+/, 'tailwind-class']]
```
  

*Spotted by [Diamond](https://app.graphite.dev/diamond/?org=windmill-labs&ref=ai-review-comment)*<i class='graphite__hidden'><br /><br />Is this helpful? React 👍 or 👎 to let us know.</i>
created_at: 2025-05-26T23:06:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-05-26T17:55:11Z
body: ## Deploying windmill with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>f67d393</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://a56da97b.windmill.pages.dev'>https://a56da97b.windmill.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://glm-fix-frontend-tailwind-au.windmill.pages.dev'>https://glm-fix-frontend-tailwind-au.windmill.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/f3802b49118373bb0dd68e319c9be207/pages/view/windmill/a56da97b-7a27-4f6e-a205-cb012a225169)

author_association: NONE
user: login: rubenfiszel
created_at: 2025-05-26T22:56:44Z
body: /ai remove tailwind_full.css asset and links in apppreview and appeditor and add tailwindUtils to the exported classes in package.json
author_association: CONTRIBUTOR
user: login: claude[bot]
created_at: 2025-05-26T22:57:55Z
body: **Claude finished @rubenfiszel's task** —— [View job](https://github.com/windmill-labs/windmill/actions/runs/15263115757)

---
### ✅ Completed: Removed tailwind_full.css and added tailwindUtils export

**Changes Made:**
- ✅ Removed `<link rel="stylesheet" href="/tailwind_full.css" />` from AppEditor.svelte and AppPreview.svelte
- ✅ Added `"./tailwindUtils"` export to package.json exports section for external consumption
- ✅ Added tailwindUtils to typesVersions section for TypeScript support
- ✅ Committed changes to the existing PR branch

**Summary:**
Successfully removed the tailwind_full.css asset references from both AppEditor and AppPreview components, and exported the tailwindUtils module via package.json so it can be imported by external consumers. The changes have been committed to the current PR branch.

**Note:** Frontend dependencies were installed successfully. You may want to run `npm run generate-backend-client` and `npm run check` manually to ensure everything builds correctly.

**Commit:** `f67d393` - Remove tailwind_full.css links and add tailwindUtils to package.json exports

---
author_association: CONTRIBUTOR