From b20ba9cfccd22159bf8263165cca76aab3147d9c Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Tue, 30 Dec 2025 13:29:03 +0200
Subject: feat: add "URL Does Not Contain" condition to rule engine (#2280)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: add "URL Does Not Contain" condition to rule engine

Add a new condition type `urlDoesNotContain` that allows users to create
rules based on URLs that do NOT contain specific strings. This enables
more flexible rule configurations, such as:

- Automatically adding bookmarks to a "Read Later" list if the URL
  does not contain "reddit.com" or "youtube.com"

Changes:
- Added `urlDoesNotContain` condition type to Zod schema
- Implemented evaluation logic in RuleEngine
- Added UI support in ConditionBuilder component
- Added translation key for new condition type
- Added test coverage for the new condition

Fixes #2259

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Mohamed Bassem <MohamedBassem@users.noreply.github.com>

* fix type link

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Mohamed Bassem <MohamedBassem@users.noreply.github.com>
---
 .../dashboard/rules/RuleEngineConditionBuilder.tsx    | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

(limited to 'apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx')

diff --git a/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx b/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx
index 8faca013..a859a4cc 100644
--- a/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx
+++ b/apps/web/components/dashboard/rules/RuleEngineConditionBuilder.tsx
@@ -54,6 +54,9 @@ export function ConditionBuilder({
       case "urlContains":
         onChange({ type: "urlContains", str: "" });
         break;
+      case "urlDoesNotContain":
+        onChange({ type: "urlDoesNotContain", str: "" });
+        break;
       case "importedFromFeed":
         onChange({ type: "importedFromFeed", feedId: "" });
         break;
@@ -88,6 +91,7 @@ export function ConditionBuilder({
   const renderConditionIcon = (type: RuleEngineCondition["type"]) => {
     switch (type) {
       case "urlContains":
+      case "urlDoesNotContain":
         return <Link className="h-4 w-4" />;
       case "importedFromFeed":
         return <Rss className="h-4 w-4" />;
@@ -118,6 +122,18 @@ export function ConditionBuilder({
           </div>
         );
 
+      case "urlDoesNotContain":
+        return (
+          <div className="mt-2">
+            <Input
+              value={value.str}
+              onChange={(e) => onChange({ ...value, str: e.target.value })}
+              placeholder="URL does not contain..."
+              className="w-full"
+            />
+          </div>
+        );
+
       case "importedFromFeed":
         return (
           <div className="mt-2">
@@ -235,6 +251,9 @@ export function ConditionBuilder({
         <SelectItem value="urlContains">
           {t("settings.rules.conditions_types.url_contains")}
         </SelectItem>
+        <SelectItem value="urlDoesNotContain">
+          {t("settings.rules.conditions_types.url_does_not_contain")}
+        </SelectItem>
         <SelectItem value="importedFromFeed">
           {t("settings.rules.conditions_types.imported_from_feed")}
         </SelectItem>
-- 
cgit v1.2.3-70-g09d2

