Repository: cline/cline
Pull request #2912: à la mode
Author: login: celestial-vault
Created: 2025-04-16T00:20:58Z | Merged: 2025-04-17T02:58:19Z

# Add Cline Rules Toggle Modal

### Description

This PR adds a UI for toggling Cline Rules on and off directly from the chat interface. It implements both global rules (applies across all workspaces) and local rules (workspace-specific) toggles in a modal similar to the existing MCP Servers toggle.

Key changes:
- Created dedicated `ClineRulesToggles` type in a shared location
- Added state management for both global and local rule toggles
- Implemented controller methods to handle rule toggle actions
- Built UI components for the rules toggle modal:
  - Main modal container with separate sections for global/local rules
  - Toggle switches with proper styling
  - Rule list component with proper rendering of active/inactive states

### Test Procedure

Manual testing was performed through these steps:

- Verified that state is preserved across shutdowns
- That new rule files show up
- That old rule files are removed
- That toggling disabled the files from being passed to the system prompt

### Type of Change

-   [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
-   [x] ✨ New feature (non-breaking change which adds functionality)
-   [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
-   [ ] ♻️ Refactor Changes
-   [ ] 💅 Cosmetic Changes
-   [ ] 📚 Documentation update
-   [ ] 🏃 Workflow Changes

### Pre-flight Checklist

-   [x] Changes are limited to a single feature, bugfix or chore (split larger changes into separate PRs)
-   [x] Tests are passing (`npm test`) and code is formatted and linted (`npm run format && npm run lint`)
-   [x] I have created a changeset using `npm run changeset` (required for user-facing changes)
-   [x] I have reviewed [contributor guidelines](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)

### Screenshots


### Additional Notes


<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> Add UI and backend support for toggling Cline Rules from the chat interface, including new components, state management, and controller methods.
> 
>   - **UI Components**:
>     - Add `ClineRulesToggleModal` in `ChatTextArea.tsx` for toggling Cline Rules.
>     - Create `RuleRow` and `RulesToggleList` components for displaying and toggling rules.
>   - **State Management**:
>     - Introduce `ClineRulesToggles` type in `cline-rules.ts`.
>     - Manage global and local rule toggles in `ExtensionStateContext.tsx`.
>   - **Controller Methods**:
>     - Add `toggleClineRule` handling in `index.ts` of `src/core/controller`.
>     - Update `getAllExtensionState` in `state.ts` to include rule toggles.
>   - **Miscellaneous**:
>     - Add `toggleClineRule` message type in `WebviewMessage.ts` and `ExtensionMessage.ts`.
> 
> <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=cline%2Fcline&utm_source=github&utm_medium=referral)<sup> for 9d35f40179cc5b265207e71a5311d60daa27f804. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->


Diff:
diff --git a/.changeset/twelve-meals-applaud.md b/.changeset/twelve-meals-applaud.md
new file mode 100644
index 00000000000..85482ce93ab
--- /dev/null
+++ b/.changeset/twelve-meals-applaud.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+Add modal UI for toggling Cline Rules
diff --git a/src/core/context/instructions/user-instructions/cline-rules.ts b/src/core/context/instructions/user-instructions/cline-rules.ts
index 19d51c490cf..9e258157cd4 100644
--- a/src/core/context/instructions/user-instructions/cline-rules.ts
+++ b/src/core/context/instructions/user-instructions/cline-rules.ts
@@ -1,10 +1,11 @@
 import path from "path"
-import { GlobalFileNames } from "../../../storage/disk"
+import { ensureRulesDirectoryExists, GlobalFileNames } from "../../../storage/disk"
 import { fileExistsAtPath, isDirectory, readDirectory } from "../../../../utils/fs"
 import { formatResponse } from "../../../prompts/responses"
 import fs from "fs/promises"
-
-export type ClineRulesToggles = Record<string, boolean> // filepath -> enabled/disabled
+import { ClineRulesToggles } from "../../../../shared/cline-rules"
+import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "../../../storage/state"
+import * as vscode from "vscode"
 
 export const getGlobalClineRules = async (globalClineRulesFilePath: string, toggles: ClineRulesToggles) => {
 	if (await fileExistsAtPath(globalClineRulesFilePath)) {
@@ -147,3 +148,28 @@ export async function synchronizeRuleToggles(
 
 	return updatedToggles
 }
+
+export async function refreshClineRulesToggles(
+	context: vscode.ExtensionContext,
+	workingDirectory: string,
+): Promise<{
+	globalToggles: ClineRulesToggles
+	localToggles: ClineRulesToggles
+}> {
+	// Global toggles
+	const globalClineRulesToggles = ((await getGlobalState(context, "globalClineRulesToggles")) as ClineRulesToggles) || {}
+	const globalClineRulesFilePath = await ensureRulesDirectoryExists()
+	const updatedGlobalToggles = await synchronizeRuleToggles(globalClineRulesFilePath, globalClineRulesToggles)
+	await updateGlobalState(context, "globalClineRulesToggles", updatedGlobalToggles)
+
+	// Local toggles
+	const localClineRulesToggles = ((await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles) || {}
+	const localClineRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.clineRules)
+	const updatedLocalToggles = await synchronizeRuleToggles(localClineRulesFilePath, localClineRulesToggles)
+	await updateWorkspaceState(context, "localClineRulesToggles", updatedLocalToggles)
+
+	return {
+		globalToggles: updatedGlobalToggles,
+		localToggles: updatedLocalToggles,
+	}
+}
diff --git a/src/core/controller/index.ts b/src/core/controller/index.ts
index 535b00ac32d..511b9584f93 100644
--- a/src/core/controller/index.ts
+++ b/src/core/controller/index.ts
@@ -40,12 +40,16 @@ import {
 	getAllExtensionState,
 	getGlobalState,
 	getSecret,
+	getWorkspaceState,
 	resetExtensionState,
 	storeSecret,
 	updateApiConfiguration,
 	updateGlobalState,
+	updateWorkspaceState,
 } from "../storage/state"
-import { Task } from "../task"
+import { Task, cwd } from "../task"
+import { ClineRulesToggles } from "../../shared/cline-rules"
+import { refreshClineRulesToggles } from "../context/instructions/user-instructions/cline-rules"
 
 /*
 https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -470,6 +474,10 @@ export class Controller {
 				const openAiModels = await this.getOpenAiModels(apiConfiguration.openAiBaseUrl, apiConfiguration.openAiApiKey)
 				this.postMessageToWebview({ type: "openAiModels", openAiModels })
 				break
+			case "refreshClineRules":
+				await refreshClineRulesToggles(this.context, cwd)
+				await this.postStateToWebview()
+				break
 			case "openImage":
 				openImage(message.text!)
 				break
@@ -647,6 +655,30 @@ export class Controller {
 				}
 				break
 			}
+			case "toggleClineRule": {
+				const { isGlobal, rulePath, enabled } = message
+				if (rulePath && typeof enabled === "boolean" && typeof isGlobal === "boolean") {
+					if (isGlobal) {
+						const toggles =
+							((await getGlobalState(this.context, "globalClineRulesToggles")) as ClineRulesToggles) || {}
+						toggles[rulePath] = enabled
+						await updateGlobalState(this.context, "globalClineRulesToggles", toggles)
+					} else {
+						const toggles =
+							((await getWorkspaceState(this.context, "localClineRulesToggles")) as ClineRulesToggles) || {}
+						toggles[rulePath] = enabled
+						await updateWorkspaceState(this.context, "localClineRulesToggles", toggles)
+					}
+					await this.postStateToWebview()
+				} else {
+					console.error("toggleClineRule: Missing or invalid parameters", {
+						rulePath,
+						isGlobal: typeof isGlobal === "boolean" ? isGlobal : `Invalid: ${typeof isGlobal}`,
+						enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`,
+					})
+				}
+				break
+			}
 			case "requestTotalTasksSize": {
 				this.refreshTotalTasksSize()
 				break
@@ -1865,8 +1897,12 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
 			mcpMarketplaceEnabled,
 			telemetrySetting,
 			planActSeparateModelsSetting,
+			globalClineRulesToggles,
 		} = await getAllExtensionState(this.context)
 
+		const localClineRulesToggles =
+			((await getWorkspaceState(this.context, "localClineRulesToggles")) as ClineRulesToggles) || {}
+
 		return {
 			version: this.context.extension?.packageJSON?.version ?? "",
 			apiConfiguration,
@@ -1889,6 +1925,8 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
 			telemetrySetting,
 			planActSeparateModelsSetting,
 			vscMachineId: vscode.env.machineId,
+			globalClineRulesToggles: globalClineRulesToggles || {},
+			localClineRulesToggles: localClineRulesToggles || {},
 		}
 	}
 
diff --git a/src/core/storage/state.ts b/src/core/storage/state.ts
index 314f0b24035..ca7c2d555d1 100644
--- a/src/core/storage/state.ts
+++ b/src/core/storage/state.ts
@@ -10,7 +10,7 @@ import { BrowserSettings } from "../../shared/BrowserSettings"
 import { ChatSettings } from "../../shared/ChatSettings"
 import { TelemetrySetting } from "../../shared/TelemetrySetting"
 import { UserInfo } from "../../shared/UserInfo"
-import { ClineRulesToggles } from "../context/instructions/user-instructions/cline-rules"
+import { ClineRulesToggles } from "../../shared/cline-rules"
 /*
 	Storage
 	https://dev.to/kompotkot/how-to-use-secretstorage-in-your-vscode-extensions-2hco
@@ -98,7 +98,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		customInstructions,
 		taskHistory,
 		autoApprovalSettings,
-		globalClineRulesToggles,
 		browserSettings,
 		chatSettings,
 		vsCodeLmModelSelector,
@@ -123,6 +122,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		sambanovaApiKey,
 		planActSeparateModelsSettingRaw,
 		favoritedModelIds,
+		globalClineRulesToggles,
 	] = await Promise.all([
 		getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
 		getGlobalState(context, "apiModelId") as Promise<string | undefined>,
@@ -169,7 +169,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		getGlobalState(context, "customInstructions") as Promise<string | undefined>,
 		getGlobalState(context, "taskHistory") as Promise<HistoryItem[] | undefined>,
 		getGlobalState(context, "autoApprovalSettings") as Promise<AutoApprovalSettings | undefined>,
-		getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
 		getGlobalState(context, "browserSettings") as Promise<BrowserSettings | undefined>,
 		getGlobalState(context, "chatSettings") as Promise<ChatSettings | undefined>,
 		getGlobalState(context, "vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
@@ -194,6 +193,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		getSecret(context, "sambanovaApiKey") as Promise<string | undefined>,
 		getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
 		getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
+		getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
 	])
 
 	let apiProvider: ApiProvider
@@ -210,6 +210,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		}
 	}
 
+	const localClineRulesToggles = (await getWorkspaceState(context, "localClineRulesToggles")) as ClineRulesToggles
+
 	const o3MiniReasoningEffort = vscode.workspace.getConfiguration("cline.modelSettings.o3Mini").get("reasoningEffort", "medium")
 
 	const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get<boolean>("mcpMarketplace.enabled", true)
@@ -294,7 +296,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		customInstructions,
 		taskHistory,
 		autoApprovalSettings: autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS, // default value can be 0 or empty string
-		clineRulesToggles: globalClineRulesToggles || {},
+		globalClineRulesToggles: globalClineRulesToggles || {},
+		localClineRulesToggles: localClineRulesToggles || {},
 		browserSettings: { ...DEFAULT_BROWSER_SETTINGS, ...browserSettings }, // this will ensure that older versions of browserSettings (e.g. before remoteBrowserEnabled was added) are merged with the default values (false for remoteBrowserEnabled)
 		chatSettings: chatSettings || DEFAULT_CHAT_SETTINGS,
 		userInfo,
diff --git a/src/core/task/index.ts b/src/core/task/index.ts
index 2be24fbaf1c..33d2c817ca4 100644
--- a/src/core/task/index.ts
+++ b/src/core/task/index.ts
@@ -77,21 +77,20 @@ import {
 	ensureTaskDirectoryExists,
 	getSavedApiConversationHistory,
 	getSavedClineMessages,
-	GlobalFileNames,
 	saveApiConversationHistory,
 	saveClineMessages,
 } from "../storage/disk"
-import { McpHub } from "../../services/mcp/McpHub"
-import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
 import {
-	ClineRulesToggles,
 	getGlobalClineRules,
 	getLocalClineRules,
-	synchronizeRuleToggles,
+	refreshClineRulesToggles,
 } from "../context/instructions/user-instructions/cline-rules"
-import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "../storage/state"
+import { getGlobalState } from "../storage/state"
+import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
+import { McpHub } from "../../services/mcp/McpHub"
 
-const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
+export const cwd =
+	vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
 
 type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
 type UserContent = Array<Anthropic.ContentBlockParam>
@@ -1286,19 +1285,12 @@ export class Task {
 				? `# Preferred Language\n\nSpeak in ${preferredLanguage}.`
 				: ""
 
-		const globalClineRulesToggles =
-			((await getGlobalState(this.getContext(), "globalClineRulesToggles")) as ClineRulesToggles) || {}
+		const { globalToggles, localToggles } = await refreshClineRulesToggles(this.getContext(), cwd)
+
 		const globalClineRulesFilePath = await ensureRulesDirectoryExists()
-		const updatedGlobalToggles = await synchronizeRuleToggles(globalClineRulesFilePath, globalClineRulesToggles)
-		await updateGlobalState(this.getContext(), "globalClineRulesToggles", updatedGlobalToggles)
-		const globalClineRulesFileInstructions = await getGlobalClineRules(globalClineRulesFilePath, updatedGlobalToggles)
-
-		const localClineRulesToggles =
-			((await getWorkspaceState(this.getContext(), "localClineRulesToggles")) as ClineRulesToggles) || {}
-		const localClineRulesFilePath = path.resolve(cwd, GlobalFileNames.clineRules)
-		const updatedLocalToggles = await synchronizeRuleToggles(localClineRulesFilePath, localClineRulesToggles)
-		await updateWorkspaceState(this.getContext(), "localClineRulesToggles", updatedLocalToggles)
-		const localClineRulesFileInstructions = await getLocalClineRules(cwd, updatedLocalToggles)
+		const globalClineRulesFileInstructions = await getGlobalClineRules(globalClineRulesFilePath, globalToggles)
+
+		const localClineRulesFileInstructions = await getLocalClineRules(cwd, localToggles)
 
 		const clineIgnoreContent = this.clineIgnoreController.clineIgnoreContent
 		let clineIgnoreInstructions: string | undefined
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index d3f4e214034..18d9f5d3f36 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -9,6 +9,7 @@ import { HistoryItem } from "./HistoryItem"
 import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem, McpDownloadResponse, McpViewTab } from "./mcp"
 import { TelemetrySetting } from "./TelemetrySetting"
 import type { BalanceResponse, UsageTransaction, PaymentTransaction } from "../shared/ClineAccount"
+import { ClineRulesToggles } from "./cline-rules"
 
 // webview will hold state
 export interface ExtensionMessage {
@@ -146,6 +147,8 @@ export interface ExtensionState {
 	}
 	version: string
 	vscMachineId: string
+	globalClineRulesToggles: ClineRulesToggles
+	localClineRulesToggles: ClineRulesToggles
 }
 
 export interface ClineMessage {
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index d7ab58c6742..75ca4e8c39d 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -32,6 +32,7 @@ export interface WebviewMessage {
 		| "refreshOpenRouterModels"
 		| "refreshRequestyModels"
 		| "refreshOpenAiModels"
+		| "refreshClineRules"
 		| "openMcpSettings"
 		| "restartMcpServer"
 		| "deleteMcpServer"
@@ -81,6 +82,8 @@ export interface WebviewMessage {
 		| "searchFiles"
 		| "toggleFavoriteModel"
 		| "grpc_request"
+		| "toggleClineRule"
+
 	// | "relaunchChromeDebugMode"
 	text?: string
 	uris?: string[] // Used for getRelativePaths
@@ -123,6 +126,10 @@ export interface WebviewMessage {
 		message: any // JSON serialized protobuf message
 		request_id: string // For correlating requests and responses
 	}
+	// For toggleClineRule
+	isGlobal?: boolean
+	rulePath?: string
+	enabled?: boolean
 }
 
 export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
diff --git a/src/shared/cline-rules.ts b/src/shared/cline-rules.ts
new file mode 100644
index 00000000000..74adb1a3fe4
--- /dev/null
+++ b/src/shared/cline-rules.ts
@@ -0,0 +1 @@
+export type ClineRulesToggles = Record<string, boolean> // filepath -> enabled/disabled
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index 75d4ba2b4eb..86d3d1c71e4 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -26,6 +26,7 @@ import { MAX_IMAGES_PER_MESSAGE } from "@/components/chat/ChatView"
 import ContextMenu from "@/components/chat/ContextMenu"
 import { ChatSettings } from "@shared/ChatSettings"
 import ServersToggleModal from "./ServersToggleModal"
+import ClineRulesToggleModal from "../cline-rules/ClineRulesToggleModal"
 
 interface ChatTextAreaProps {
 	inputValue: string
@@ -1225,6 +1226,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
 							</ButtonContainer>
 						</VSCodeButton>
 						<ServersToggleModal />
+						<ClineRulesToggleModal />
 
 						<ModelContainer ref={modelSelectorRef}>
 							<ModelButtonWrapper ref={buttonRef}>
diff --git a/webview-ui/src/components/chat/ServersToggleModal.tsx b/webview-ui/src/components/chat/ServersToggleModal.tsx
index 66571ae6cff..086506facb2 100644
--- a/webview-ui/src/components/chat/ServersToggleModal.tsx
+++ b/webview-ui/src/components/chat/ServersToggleModal.tsx
@@ -74,7 +74,7 @@ const ServersToggleModal: React.FC = () => {
 					/>
 
 					<div className="flex justify-between items-center mb-2.5">
-						<div className="m-0">MCP Servers</div>
+						<div className="m-0 text-base font-semibold">MCP Servers</div>
 						<VSCodeButton
 							appearance="icon"
 							onClick={() => {
diff --git a/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx b/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx
new file mode 100644
index 00000000000..ea3c66a80e8
--- /dev/null
+++ b/webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx
@@ -0,0 +1,133 @@
+import React, { useRef, useState, useEffect } from "react"
+import { useClickAway, useWindowSize } from "react-use"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
+import { vscode } from "@/utils/vscode"
+import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
+import RulesToggleList from "./RulesToggleList"
+
+const ClineRulesToggleModal: React.FC = () => {
+	const { globalClineRulesToggles = {}, localClineRulesToggles = {} } = useExtensionState()
+	const [isVisible, setIsVisible] = useState(false)
+	const buttonRef = useRef<HTMLDivElement>(null)
+	const modalRef = useRef<HTMLDivElement>(null)
+	const { width: viewportWidth, height: viewportHeight } = useWindowSize()
+	const [arrowPosition, setArrowPosition] = useState(0)
+	const [menuPosition, setMenuPosition] = useState(0)
+
+	useEffect(() => {
+		if (isVisible) {
+			vscode.postMessage({ type: "refreshClineRules" })
+		}
+	}, [isVisible])
+
+	// Format global rules for display with proper typing
+	const globalRules = Object.entries(globalClineRulesToggles || {})
+		.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
+		.sort(([a], [b]) => a.localeCompare(b))
+
+	// Format local rules for display with proper typing
+	const localRules = Object.entries(localClineRulesToggles || {})
+		.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
+		.sort(([a], [b]) => a.localeCompare(b))
+
+	// Handle toggle rule
+	const toggleRule = (isGlobal: boolean, rulePath: string, enabled: boolean) => {
+		vscode.postMessage({
+			type: "toggleClineRule",
+			isGlobal,
+			rulePath,
+			enabled,
+		})
+	}
+
+	// Close modal when clicking outside
+	useClickAway(modalRef, () => {
+		setIsVisible(false)
+	})
+
+	// Calculate positions for modal and arrow
+	useEffect(() => {
+		if (isVisible && buttonRef.current) {
+			const buttonRect = buttonRef.current.getBoundingClientRect()
+			const buttonCenter = buttonRect.left + buttonRect.width / 2
+			const rightPosition = document.documentElement.clientWidth - buttonCenter - 5
+
+			setArrowPosition(rightPosition)
+			setMenuPosition(buttonRect.top + 1)
+		}
+	}, [isVisible, viewportWidth, viewportHeight])
+
+	return (
+		<div ref={modalRef}>
+			<div ref={buttonRef} className="inline-flex min-w-0 max-w-full">
+				<VSCodeButton
+					appearance="icon"
+					aria-label="Cline Rules"
+					onClick={() => setIsVisible(!isVisible)}
+					style={{ padding: "0px 0px", height: "20px" }}>
+					<div className="flex items-center gap-1 text-xs whitespace-nowrap min-w-0 w-full">
+						<span className="codicon codicon-law flex items-center" style={{ fontSize: "12.5px", marginBottom: 1 }} />
+					</div>
+				</VSCodeButton>
+			</div>
+
+			{isVisible && (
+				<div
+					className="fixed left-[15px] right-[15px] border border-[var(--vscode-editorGroup-border)] p-3 rounded z-[1000] overflow-y-auto"
+					style={{
+						bottom: `calc(100vh - ${menuPosition}px + 6px)`,
+						background: CODE_BLOCK_BG_COLOR,
+						maxHeight: "calc(100vh - 100px)",
+						overscrollBehavior: "contain",
+					}}>
+					<div
+						className="fixed w-[10px] h-[10px] z-[-1] rotate-45 border-r border-b border-[var(--vscode-editorGroup-border)]"
+						style={{
+							bottom: `calc(100vh - ${menuPosition}px)`,
+							right: arrowPosition,
+							background: CODE_BLOCK_BG_COLOR,
+						}}
+					/>
+
+					<div className="flex justify-between items-center mb-2.5">
+						<div className="m-0 text-base font-semibold">Cline Rules</div>
+
+						<VSCodeButton
+							appearance="icon"
+							onClick={() => {
+								vscode.postMessage({
+									type: "openExtensionSettings",
+								})
+								setIsVisible(false)
+							}}>
+							{/* <span className="codicon codicon-gear text-[10px]"></span> */}
+						</VSCodeButton>
+					</div>
+
+					{/* Global Rules Section */}
+					<div className="mb-3">
+						<div className="text-sm font-normal mb-2">Global Rules</div>
+						<RulesToggleList
+							rules={globalRules}
+							toggleRule={(rulePath, enabled) => toggleRule(true, rulePath, enabled)}
+							listGap="small"
+						/>
+					</div>
+
+					{/* Local Rules Section */}
+					<div style={{ marginBottom: -10 }}>
+						<div className="text-sm font-normal mb-2">Workspace Rules</div>
+						<RulesToggleList
+							rules={localRules}
+							toggleRule={(rulePath, enabled) => toggleRule(false, rulePath, enabled)}
+							listGap="small"
+						/>
+					</div>
+				</div>
+			)}
+		</div>
+	)
+}
+
+export default ClineRulesToggleModal
diff --git a/webview-ui/src/components/cline-rules/RuleRow.tsx b/webview-ui/src/components/cline-rules/RuleRow.tsx
new file mode 100644
index 00000000000..cc48ba991ad
--- /dev/null
+++ b/webview-ui/src/components/cline-rules/RuleRow.tsx
@@ -0,0 +1,49 @@
+const RuleRow: React.FC<{
+	rulePath: string
+	enabled: boolean
+	toggleRule: (rulePath: string, enabled: boolean) => void
+}> = ({ rulePath, enabled, toggleRule }) => {
+	// Get the filename from the path for display
+	const displayName = rulePath.split("/").pop() || rulePath
+
+	return (
+		<div className="mb-2.5">
+			<div
+				className={`flex items-center p-2 rounded bg-[var(--vscode-textCodeBlock-background)] ${
+					enabled ? "opacity-100" : "opacity-60"
+				}`}>
+				<span className="flex-1 overflow-hidden break-all whitespace-normal flex items-center mr-1" title={rulePath}>
+					{displayName}
+				</span>
+
+				{/* Toggle Switch */}
+				<div className="flex items-center ml-2">
+					<div
+						role="switch"
+						aria-checked={enabled}
+						tabIndex={0}
+						className={`w-[20px] h-[10px] rounded-[5px] relative cursor-pointer transition-colors duration-200 ${
+							enabled
+								? "bg-[var(--vscode-testing-iconPassed)] opacity-90"
+								: "bg-[var(--vscode-titleBar-inactiveForeground)] opacity-50"
+						}`}
+						onClick={() => toggleRule(rulePath, !enabled)}
+						onKeyDown={(e) => {
+							if (e.key === "Enter" || e.key === " ") {
+								e.preventDefault()
+								toggleRule(rulePath, !enabled)
+							}
+						}}>
+						<div
+							className={`w-[6px] h-[6px] bg-white border border-[#66666699] rounded-full absolute top-[1px] transition-all duration-200 ${
+								enabled ? "left-[12px]" : "left-[2px]"
+							}`}
+						/>
+					</div>
+				</div>
+			</div>
+		</div>
+	)
+}
+
+export default RuleRow
diff --git a/webview-ui/src/components/cline-rules/RulesToggleList.tsx b/webview-ui/src/components/cline-rules/RulesToggleList.tsx
new file mode 100644
index 00000000000..5e244bed738
--- /dev/null
+++ b/webview-ui/src/components/cline-rules/RulesToggleList.tsx
@@ -0,0 +1,31 @@
+import RuleRow from "./RuleRow"
+
+const RulesToggleList = ({
+	rules,
+	toggleRule,
+	listGap = "medium",
+}: {
+	rules: [string, boolean][]
+	toggleRule: (rulePath: string, enabled: boolean) => void
+	listGap?: "small" | "medium" | "large"
+}) => {
+	const gapClasses = {
+		small: "gap-0",
+		medium: "gap-2.5",
+		large: "gap-5",
+	}
+
+	const gapClass = gapClasses[listGap]
+
+	return rules.length > 0 ? (
+		<div className={`flex flex-col ${gapClass}`}>
+			{rules.map(([rulePath, enabled]) => (
+				<RuleRow key={rulePath} rulePath={rulePath} enabled={enabled} toggleRule={toggleRule} />
+			))}
+		</div>
+	) : (
+		<div className="flex flex-col items-center gap-3 my-5 text-[var(--vscode-descriptionForeground)]">No rules found</div>
+	)
+}
+
+export default RulesToggleList
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 83f76fb90a6..9e05269ec0b 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -17,7 +17,7 @@ import { vscode } from "../utils/vscode"
 import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
 import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
 import { TelemetrySetting } from "@shared/TelemetrySetting"
-
+import { ClineRulesToggles } from "@shared/cline-rules"
 interface ExtensionStateContextType extends ExtensionState {
 	didHydrateState: boolean
 	showWelcome: boolean
@@ -53,6 +53,8 @@ export const ExtensionStateContextProvider: React.FC<{
 		telemetrySetting: "unset",
 		vscMachineId: "",
 		planActSeparateModelsSetting: true,
+		globalClineRulesToggles: {},
+		localClineRulesToggles: {},
 	})
 	const [didHydrateState, setDidHydrateState] = useState(false)
 	const [showWelcome, setShowWelcome] = useState(false)
@@ -128,6 +130,7 @@ export const ExtensionStateContextProvider: React.FC<{
 				})
 				break
 			}
+
 			case "openRouterModels": {
 				const updatedModels = message.openRouterModels ?? {}
 				setOpenRouterModels({
@@ -184,6 +187,8 @@ export const ExtensionStateContextProvider: React.FC<{
 		mcpMarketplaceCatalog,
 		filePaths,
 		totalTasksSize,
+		globalClineRulesToggles: state.globalClineRulesToggles || {},
+		localClineRulesToggles: state.localClineRulesToggles || {},
 		setApiConfiguration: (value) =>
 			setState((prevState) => ({
 				...prevState,


Reviews:
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Reviewed everything up to 5670a3009f10777b515b8a16a26a4c729959f880 in <ellipsistime>3 minutes and 20 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `722` lines of code in `16` files
 - Skipped `0` files when reviewing.
 - Skipped posting `15` drafted comments based on config settings.


<details>
<summary>1. <code>src/utils/fs.ts:87</code></summary>

- **Draft comment:** 
The use of `recursive: true` in readdir is powerful but may cause performance/memory issues in very large directory trees. Consider commenting/documenting expected use cases or adding safeguards.
- **Reason this comment was not posted:** 
Comment was on unchanged code.

</details>

<details>
<summary>2. <code>src/core/task/index.ts:1267</code></summary>

- **Draft comment:** 
The generator function 'attemptApiRequest' is quite complex with multiple nested try/catch blocks and retry logic. Consider refactoring into smaller, well-documented helper functions to improve readability and maintainability.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>3. <code>webview-ui/src/components/chat/ChatTextArea.tsx:392</code></summary>

- **Draft comment:** 
The key-press handling in the text area (e.g. Arrow keys, Backspace logic) is quite elaborate. Verify that all dependency arrays in useCallback hooks (like handleKeyDown) are accurate to avoid stale closures.
- **Reason this comment was not posted:** 
Confidence changes required: `50%` <= threshold `50%`
None

</details>

<details>
<summary>4. <code>webview-ui/src/context/ExtensionStateContext.tsx:178</code></summary>

- **Draft comment:** 
Consider memoizing the context value (using useMemo) before passing it to the provider to avoid unnecessary re-renders of consumers.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>5. <code>src/utils/fs.ts:85</code></summary>

- **Draft comment:** 
Review the filtering in readDirectory: filtering out entries whose name starts with a dot is applied uniformly. Confirm if this hidden file exclusion is desired for all use cases. Also, ensure that using 'file.parentPath' is reliable across platforms.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>6. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:29</code></summary>

- **Draft comment:** 
toggleRule sends a postMessage without handling potential errors. Consider adding error handling or user feedback in case the message fails to send.
- **Reason this comment was not posted:** 
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50%
1. postMessage in VSCode webviews is a core communication mechanism that rarely fails. 2. Error handling would add complexity for an edge case. 3. If message sending fails, it would likely indicate a more fundamental problem with the webview. 4. The UI would naturally show the unchanged toggle state if the message failed. 5. This seems like over-engineering for a simple toggle operation.
The comment raises a valid point about defensive programming. Failed communication could lead to inconsistent state between the UI and actual settings.
The natural UI feedback (toggle state) already handles failures implicitly, and adding explicit error handling would add complexity with little benefit in this VSCode webview context.
The comment should be removed as it suggests adding unnecessary complexity for an edge case that is already handled implicitly by the UI.

</details>

<details>
<summary>7. <code>webview-ui/src/components/cline-rules/RuleRow.tsx:22</code></summary>

- **Draft comment:** 
Ensure the toggle (role='switch') in RuleRow has accessible focus styles and keyboard interactions. It looks correct but double-check for accessibility.
- **Reason this comment was not posted:** 
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50%
The comment is asking to "ensure" and "double-check" accessibility, which violates our rule about verification requests. Moreover, the code already implements proper accessibility features - role, aria-checked, keyboard focus, and key handlers. The only potential missing item might be focus styles, but those are likely handled by the VSCode theme variables being used.
I might be missing some advanced accessibility requirements. There could be other ARIA attributes or keyboard interactions that should be implemented.
While there might be additional accessibility features possible, the comment isn't specific about what's missing. The core accessibility requirements are already implemented.
Delete this comment. It's a verification request and the code already implements proper accessibility features. If there are specific missing accessibility requirements, they should be stated explicitly.

</details>

<details>
<summary>8. <code>webview-ui/src/context/ExtensionStateContext.tsx:190</code></summary>

- **Draft comment:** 
The context now includes globalClineRulesToggles and localClineRulesToggles. Verify that their updates and persistence (via update functions) are correctly handled across the extension.
- **Reason this comment was not posted:** 
Comment did not seem useful. Confidence is useful = `0%` <= threshold `50%`
This comment is asking the PR author to verify the updates and persistence of certain variables, which falls under the rule of not asking the author to ensure behavior is intended or tested. It doesn't provide a specific suggestion or point out a specific issue, so it should be removed.

</details>

<details>
<summary>9. <code>webview-ui/src/components/chat/ChatTextArea.tsx:29</code></summary>

- **Draft comment:** 
ClineRulesToggleModal is integrated into the chat UI. Please ensure its styling and layering (z-index and positioning) harmonize with existing chat components.
- **Reason this comment was not posted:** 
Comment did not seem useful. Confidence is useful = `0%` <= threshold `50%`
The comment is asking the PR author to ensure that the styling and layering of a component harmonize with existing components. This falls under the category of asking the author to ensure something, which is against the rules.

</details>

<details>
<summary>10. <code>webview-ui/src/components/cline-rules/RulesToggleList.tsx:22</code></summary>

- **Draft comment:** 
The key for each RuleRow is the file path. Ensure these rulePath values remain stable (e.g. consistent formatting) so that React keys do not change unexpectedly.
- **Reason this comment was not posted:** 
Comment did not seem useful. Confidence is useful = `40%` <= threshold `50%`
The comment is suggesting to ensure that the `rulePath` values remain stable to prevent React keys from changing unexpectedly. This is a suggestion to maintain consistency in the code, which is a valid concern. However, it does not directly suggest a specific code change or ask for confirmation of an intentional action. It is more of a general advice, which might not be necessary in a code review context.

</details>

<details>
<summary>11. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:44</code></summary>

- **Draft comment:** 
The modal calculates its arrow and menu positions based on the button's bounding rect. Consider handling window resize events or dynamic changes to ensure positions remain correct.
- **Reason this comment was not posted:** 
Comment looked like it was already resolved.

</details>

<details>
<summary>12. <code>webview-ui/src/context/ExtensionStateContext.tsx:175</code></summary>

- **Draft comment:** 
The effect sending a 'webviewDidLaunch' message on mount works correctly, but verify that it does not trigger unnecessary rerenders or create loops in state updates.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>13. <code>src/core/storage/state-keys.ts:65</code></summary>

- **Draft comment:** 
Typo alert: The key 'qwenApiLine' (line 65) appears to be inconsistent with the naming pattern for API keys in this file. Consider confirming if this should be 'qwenApiKey' instead.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>14. <code>src/shared/ExtensionMessage.ts:28</code></summary>

- **Draft comment:** 
There's a potential typographical error in the property union type. The type 'requestyModels' might be a typo. Consider changing it to 'requestModels' or the intended correct spelling to maintain consistency in naming.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>15. <code>src/utils/fs.ts:37</code></summary>

- **Draft comment:** 
The doc comment for fileExistsAtPath uses '@param path' while the function parameter is actually named 'filePath'. Please update the documentation to consistently use 'filePath'.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>


Workflow ID: <workflowid>`wflow_4qM21vaZQRSGwWLb`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T00:24:23Z
user: login: dcbartlett
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T00:48:57Z
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Incremental review on e66c5f253580e326a9283c7215fe8c643d7c48e6 in <ellipsistime>54 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `16` lines of code in `1` files
 - Skipped `0` files when reviewing.
 - Skipped posting `2` drafted comments based on config settings.


<details>
<summary>1. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:64</code></summary>

- **Draft comment:** 
Icon type changed from 'codicon-file-code' to 'codicon-law'. Ensure this icon is consistent with UI guidelines and available in the icon set.
- **Reason this comment was not posted:** 
Comment did not seem useful. Confidence is useful = `40%` <= threshold `50%`
The comment is asking the author to ensure consistency with UI guidelines and availability in the icon set. This is similar to asking the author to double-check or confirm something, which violates the rules. However, it does point out a specific change in the icon type, which could be useful if it suggests a potential oversight.

</details>

<details>
<summary>2. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:64</code></summary>

- **Draft comment:** 
Icon update: Changing from 'codicon-file-code' to 'codicon-law' makes sense for a rules toggle. Make sure that the new icon displays correctly across themes and maintains accessibility (e.g., consider aria-hidden if decorative).
- **Reason this comment was not posted:** 
Comment did not seem useful. Confidence is useful = `0%` <= threshold `50%`
The comment is asking the author to ensure that the new icon displays correctly and maintains accessibility. This falls under asking the author to ensure behavior, which is not allowed according to the rules.

</details>


Workflow ID: <workflowid>`wflow_P9gtvuWAqqqzbDAo`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T18:07:48Z
user: login: dcbartlett
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T22:52:40Z
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Incremental review on 3b24825429c5bee83fe0b09a621cd9545c25a9e1 in <ellipsistime>1 minute and 6 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `163` lines of code in `2` files
 - Skipped `0` files when reviewing.
 - Skipped posting `15` drafted comments based on config settings.


<details>
<summary>1. <code>src/core/task/index.ts:99</code></summary>

- **Draft comment:** 
The Task class is extremely large and handles many responsibilities. Consider refactoring into smaller, focused modules for improved maintainability.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>2. <code>src/core/task/index.ts:2510</code></summary>

- **Draft comment:** 
Magic number timeouts (e.g. setTimeoutPromise(3500)) are used. Define named constants for these values for clarity and easier tuning.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>3. <code>src/core/task/index.ts:2420</code></summary>

- **Draft comment:** 
The nested switch-case logic in tool execution (e.g. for 'execute_command') is very complex. Consider extracting helper functions to simplify and improve readability.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>4. <code>src/core/task/index.ts:3568</code></summary>

- **Draft comment:** 
The getEnvironmentDetails function performs extensive filesystem operations and terminal queries. Review for potential performance issues and consider caching results where possible.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>5. <code>src/core/task/index.ts:3146</code></summary>

- **Draft comment:** 
The recursivelyMakeClineRequests function contains deep nesting with multiple try-catch blocks, making error handling and control flow complex. Consider refactoring into smaller functions.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>6. <code>src/core/task/index.ts:1340</code></summary>

- **Draft comment:** 
The attemptApiRequest function uses a complex streaming logic with multiple yield statements. Additional inline comments or refactoring would help future maintainers understand this flow better.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>7. <code>src/core/task/index.ts:1122</code></summary>

- **Draft comment:** 
The executeCommandTool function executes shell commands using user input without explicit sanitization. Ensure commands are properly sanitized to avoid shell injection vulnerabilities.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>8. <code>src/core/task/index.ts:1440</code></summary>

- **Draft comment:** 
Consider using a shallower copy (or refactoring state updates) instead of cloneDeep here to improve performance with large data.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>9. <code>src/core/task/index.ts:1407</code></summary>

- **Draft comment:** 
Recursive re-invocation of attemptApiRequest in the catch block may lead to infinite retries. Consider adding a retry limit or backoff mechanism.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>10. <code>src/core/task/index.ts:1498</code></summary>

- **Draft comment:** 
The large switch-case for tool execution is complex; consider refactoring these cases into separate, modular helper functions to improve readability and maintainability.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>11. <code>src/core/task/index.ts:2952</code></summary>

- **Draft comment:** 
There is a block of commented-out legacy code in the attempt_completion section. Remove these obsolete comments to clean up the code.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>12. <code>src/core/task/index.ts:2755</code></summary>

- **Draft comment:** 
Avoid swallowing errors silently; consider handling or logging errors instead of using empty catch blocks (e.g. .catch(() => {})).
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>13. <code>src/core/task/index.ts:3631</code></summary>

- **Draft comment:** 
Remove obsolete commented-out diagnostic code to improve clarity.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>14. <code>src/core/task/index.ts:3616</code></summary>

- **Draft comment:** 
Using a fixed delay (setTimeoutPromise(300)) to wait for terminals may lead to flaky behavior; consider a more reliable event-driven approach.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>15. <code>src/core/task/index.ts:3471</code></summary>

- **Draft comment:** 
Multiple calls to state updates (saveClineMessagesAndUpdateHistory and postStateToWebview) occur in rapid succession. Consider batching these updates to reduce potential performance overhead.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>


Workflow ID: <workflowid>`wflow_NHFyvnvsJV0tZMIB`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T00:38:14Z
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Incremental review on 17ef209cc011c4629284168c9491b2d3a7365d04 in <ellipsistime>1 minute and 46 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `20` lines of code in `1` files
 - Skipped `0` files when reviewing.
 - Skipped posting `2` drafted comments based on config settings.


<details>
<summary>1. <code>src/core/storage/state.ts:98</code></summary>

- **Draft comment:** 
Removed globalClineRulesToggles from state retrieval but it's still referenced in the return object (line 299). Update the return object or adjust the destructuring to avoid potential undefined reference.
- **Reason this comment was not posted:** 
Comment looked like it was already resolved.

</details>

<details>
<summary>2. <code>src/core/storage/state.ts:98</code></summary>

- **Draft comment:** 
globalClineRulesToggles is removed from the global state retrieval (lines 98 and 170) but is still referenced in the returned object (e.g. line 299). This means any persisted global toggles will always default to {}. Please confirm if this change is intentional.
- **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 suggests there's an inconsistency where globalClineRulesToggles is removed but still used. However, looking at the full file, I can see that globalClineRulesToggles is still being fetched and included - it was just moved to a different position in the array. The comment is based on an incorrect assumption.
Could I be missing some other part of the codebase where this variable is used? Could there be a reason why moving its position in the array would cause problems?
The order of elements in a Promise.all() array doesn't matter - the destructured array positions just need to match the Promise array positions, which they do here. The value is still being fetched and used correctly.
The comment should be deleted because it incorrectly claims globalClineRulesToggles was removed when it was actually just moved to a different position in the array.

</details>


Workflow ID: <workflowid>`wflow_fvZPIwnsYncxSJvi`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T00:42:25Z
user: login: dcbartlett
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T01:54:25Z
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Incremental review on 9d35f40179cc5b265207e71a5311d60daa27f804 in <ellipsistime>1 minute and 32 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `164` lines of code in `5` files
 - Skipped `0` files when reviewing.
 - Skipped posting `7` drafted comments based on config settings.


<details>
<summary>1. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:18</code></summary>

- **Draft comment:** 
Good practice refreshing rules when modal becomes visible. Consider ensuring that the vscode object is stable or memoized so that the effect doesn’t trigger unexpectedly.
- **Reason this comment was not posted:** 
Comment looked like it was already resolved.

</details>

<details>
<summary>2. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:62</code></summary>

- **Draft comment:** 
The modal container is rendered without explicit ARIA roles for a dialog. Consider adding role="dialog" and appropriate aria-label or aria-modal attributes to improve accessibility.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>3. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:75</code></summary>

- **Draft comment:** 
The inline styling for the modal position and arrow positioning is computed from DOM measurements. Ensure that these calculations cover edge cases and remain robust.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>4. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:18</code></summary>

- **Draft comment:** 
The useEffect hook sends a 'refreshClineRules' message every time the modal becomes visible. Consider debouncing or checking if a refresh is really needed to avoid redundant messages if visibility toggles rapidly.
- **Reason this comment was not posted:** 
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50%
The comment is speculative - it suggests debouncing "if visibility toggles rapidly" but there's no evidence this is actually a problem. The modal visibility is controlled by a button click and clicking away, which are inherently user-driven actions that aren't likely to occur rapidly. The refresh is also only sent when isVisible becomes true, not on every visibility toggle.
Perhaps there could be programmatic changes to visibility that could cause rapid toggling that I'm not seeing. The refresh operation could be expensive on the vscode side.
Without evidence that rapid toggling is occurring or that the refresh operation is causing performance issues, this is a premature optimization. The current implementation is simple and clear.
Delete the comment as it suggests a speculative optimization without evidence of an actual problem.

</details>

<details>
<summary>5. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:50</code></summary>

- **Draft comment:** 
Arrow and menu positioning is calculated using document.documentElement.clientWidth. Consider using window.innerWidth for potentially more consistent layout measurements across environments.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>6. <code>src/shared/WebviewMessage.ts:11</code></summary>

- **Draft comment:** 
The union type for the 'type' property in the WebviewMessage interface is very extensive. For better maintainability as the project grows, consider grouping related message types into subtypes or splitting the interface into more modular pieces.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>7. <code>webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx:75</code></summary>

- **Draft comment:** 
Several inline styles (e.g. for modal positioning, background, maxHeight) are directly written in the JSX. For improved maintainability and consistency, consider moving these styles to a dedicated CSS module or styled component.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>


Workflow ID: <workflowid>`wflow_L1jxw5nxK0lGkdvJ`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T02:17:50Z
user: login: celestial-vault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T02:18:08Z
user: login: canvrno
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T02:53:56Z
user: login: dcbartlett
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T02:54:14Z
user: login: celestial-vault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T02:57:38Z

Review comments:
diff_hunk: @@ -3,8 +3,7 @@ import { GlobalFileNames } from "../../../storage/disk"
 import { fileExistsAtPath, isDirectory, readDirectory } from "../../../../utils/fs"
 import { formatResponse } from "../../../prompts/responses"
 import fs from "fs/promises"
-
-export type ClineRulesToggles = Record<string, boolean> // filepath -> enabled/disabled
+import { ClineRulesToggles } from "../../../../shared/cline-rules"
path: src/core/context/instructions/user-instructions/cline-rules.ts
user: login: dcbartlett
body: Can we use an `@alias` here?
created_at: 2025-04-16T00:47:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,7 +10,7 @@ import { BrowserSettings } from "../../shared/BrowserSettings"
 import { ChatSettings } from "../../shared/ChatSettings"
 import { TelemetrySetting } from "../../shared/TelemetrySetting"
 import { UserInfo } from "../../shared/UserInfo"
-import { ClineRulesToggles } from "../context/instructions/user-instructions/cline-rules"
+import { ClineRulesToggles } from "../../shared/cline-rules"
path: src/core/storage/state.ts
user: login: dcbartlett
body: Maybe one here too?
created_at: 2025-04-16T00:48:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -84,15 +83,13 @@ import {
 	saveApiConversationHistory,
 	saveClineMessages,
 } from "../storage/disk"
-import { McpHub } from "../../services/mcp/McpHub"
-import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
 import {
-	ClineRulesToggles,
 	getGlobalClineRules,
 	getLocalClineRules,
 	synchronizeRuleToggles,
 } from "../context/instructions/user-instructions/cline-rules"
 import { getGlobalState, getWorkspaceState, updateGlobalState, updateWorkspaceState } from "../storage/state"
+import { ClineRulesToggles } from "../../shared/cline-rules"
path: src/core/task/index.ts
user: login: dcbartlett
body: Also here?
created_at: 2025-04-16T00:48:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,7 +10,7 @@ import { BrowserSettings } from "../../shared/BrowserSettings"
 import { ChatSettings } from "../../shared/ChatSettings"
 import { TelemetrySetting } from "../../shared/TelemetrySetting"
 import { UserInfo } from "../../shared/UserInfo"
-import { ClineRulesToggles } from "../context/instructions/user-instructions/cline-rules"
+import { ClineRulesToggles } from "../../shared/cline-rules"
path: src/core/storage/state.ts
user: login: celestial-vault
body: Yep yep
created_at: 2025-04-17T02:18:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,8 +47,9 @@ import {
 	updateGlobalState,
 	updateWorkspaceState,
 } from "../storage/state"
-import { Task } from "../task"
+import { Task, cwd } from "../task"
path: src/core/controller/index.ts
user: login: canvrno
body: Would recommend getWorkspacePath() in path.ts instead
created_at: 2025-04-17T02:48:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,8 +47,9 @@ import {
 	updateGlobalState,
 	updateWorkspaceState,
 } from "../storage/state"
-import { Task } from "../task"
+import { Task, cwd } from "../task"
path: src/core/controller/index.ts
user: login: celestial-vault
body: Yes I don't like the export. Will do a PR to rework this.
created_at: 2025-04-17T02:57:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-16T00:21:02Z
body: ###  🦋  Changeset detected

Latest commit: 9d35f40179cc5b265207e71a5311d60daa27f804

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name       | Type  |
| ---------- | ----- |
| claude-dev | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/cline/cline/new/cline-rules-toggle-modal?filename=.changeset/sour-keys-brush.md&value=---%0A%22claude-dev%22%3A%20patch%0A---%0A%0A%C3%A0%20la%20mode%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T00:23:42Z
body: <!-- COVERAGE_REPORT -->
## Coverage Report

### Extension Coverage

Base branch: 45%

PR branch: 45%

✅ Coverage increased or remained the same

### Webview Coverage

Base branch: 11%

PR branch: 11%

⚠️ **Warning: Coverage decreased by 0.10%**

Consider adding tests to cover your changes.

### Overall Assessment

⚠️ **Test coverage has decreased in this PR**

Please consider adding tests to maintain or improve coverage.



<sub>Last updated: 2025-04-17T02:18:50.666707</sub>
author_association: CONTRIBUTOR
user: login: dcbartlett
created_at: 2025-04-16T00:49:10Z
body: Make sure to do your checkmarks.
author_association: CONTRIBUTOR
user: login: celestial-vault
created_at: 2025-04-16T00:57:11Z
body: @dcbartlett Will be adding aliases in the next PR as a standalone
author_association: CONTRIBUTOR
user: login: celestial-vault
created_at: 2025-04-16T00:57:43Z
body: @dcbartlett Yes working on the description
author_association: CONTRIBUTOR
user: login: canvrno
created_at: 2025-04-16T22:55:57Z
body: I found one issue with the new menu not displaying rules files until a task was started, this was discussed with @celestial-vault.
author_association: CONTRIBUTOR
user: login: celestial-vault
created_at: 2025-04-17T02:57:21Z
body: > I found one issue with the new menu not displaying rules files until a task was started, this was discussed with @celestial-vault.

fixed
author_association: CONTRIBUTOR