Repository: onlook-dev/onlook
Pull request #1897: fix text tags and subdirectory
Author: login: Kitenite
Created: 2025-05-21T19:56:17Z | Merged: 2025-05-21T21:39:02Z

## Description

<!-- Provide a clear and concise description of your changes -->

## Related Issues

<!-- Link any related issues using GitHub keywords (e.g., "closes #123", "fixes #456", "related to #789") -->

## Type of Change

<!-- Put an `x` in the boxes that apply -->

- [x] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] Release
- [ ] Refactor
- [ ] Other (please describe):

## Testing

<!-- Describe the tests you ran or the steps to verify your changes -->

## Screenshots (if applicable)

<!-- Add screenshots to help explain your changes -->

## Additional Notes

<!-- Add any other context about the PR here -->

<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> Fixes text tag handling and refactors subdirectory checks using `is-subdir`, adding comprehensive tests and updating dependencies.
> 
>   - **Behavior**:
>     - Expanded `TAG_TYPES` in `index.tsx` to include more HTML tags like `h3`, `h4`, `p`, `span`, etc.
>     - Replaced custom `isSubdirectory` function with `is-subdir` package in `helpers.ts` and `index.ts`.
>   - **Utilities**:
>     - Added `path.ts` with `isSubdirectory` function using `is-subdir` for enhanced path handling.
>     - Updated `index.ts` to export new path utilities.
>   - **Testing**:
>     - Added `path.test.ts` with extensive test cases for `isSubdirectory` covering various path scenarios.
>   - **Dependencies**:
>     - Added `is-subdir` to `package.json` dependencies.
>     - Updated `bun.lock` to include `is-subdir` and its dependencies.
> 
> <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=onlook-dev%2Fonlook&utm_source=github&utm_medium=referral)<sup> for 9b6292d1dcbe42536b616bd5f2bbde58cde39e1a. You can [customize](https://app.ellipsis.dev/onlook-dev/settings/summaries) this summary. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->

Linked issue #123: [FEAT] Add better onboarding docs
#### Describe the feature
Add framework documentation
- Next.js
- Remix
- Webpack
- Babel

Diff:
diff --git a/apps/web/client/src/app/project/[id]/_components/editor-bar/index.tsx b/apps/web/client/src/app/project/[id]/_components/editor-bar/index.tsx
index 5b19878aad..7a76535f6b 100644
--- a/apps/web/client/src/app/project/[id]/_components/editor-bar/index.tsx
+++ b/apps/web/client/src/app/project/[id]/_components/editor-bar/index.tsx
@@ -10,7 +10,7 @@ import { ImgSelected } from './img-selected';
 import { TextSelected } from './text-selected';
 
 const TAG_TYPES: Record<string, string[]> = {
-    text: ['h1', 'h2'],
+    text: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'a', 'strong', 'b', 'em', 'i', 'mark', 'code', 'small', 'blockquote', 'pre', 'time', 'sub', 'sup', 'del', 'ins', 'u', 'abbr', 'cite', 'q'],
     div: ['div'],
     image: ['img'],
     video: ['video'],
diff --git a/apps/web/client/src/components/store/editor/sandbox/helpers.ts b/apps/web/client/src/components/store/editor/sandbox/helpers.ts
index 70ffb9fdb3..a3adee78b3 100644
--- a/apps/web/client/src/components/store/editor/sandbox/helpers.ts
+++ b/apps/web/client/src/components/store/editor/sandbox/helpers.ts
@@ -2,6 +2,7 @@ import path from 'path';
 import parserEstree from 'prettier/plugins/estree';
 import parserTypescript from 'prettier/plugins/typescript';
 import prettier from 'prettier/standalone';
+import isSubdir from 'is-subdir';
 
 const SANDBOX_ROOT = '/project/sandbox';
 
@@ -11,21 +12,6 @@ export function normalizePath(p: string): string {
     return relative.replace(/\\/g, '/'); // Always POSIX style
 }
 
-export function isSubdirectory(filePath: string, directories: string[]): boolean {
-    // Normalize the file path by replacing backslashes with forward slashes
-    const normalizedFilePath = path.resolve(filePath.replace(/\\/g, '/'));
-
-    for (const directory of directories) {
-        // Normalize the directory path by replacing backslashes with forward slashes
-        const normalizedDir = path.resolve(directory.replace(/\\/g, '/'));
-        const relative = path.relative(normalizedDir, normalizedFilePath);
-        if (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))) {
-            return true;
-        }
-    }
-    return false;
-}
-
 export async function formatContent(filePath: string, content: string): Promise<string> {
     try {
         // Use browser standalone version with necessary plugins
diff --git a/apps/web/client/src/components/store/editor/sandbox/index.ts b/apps/web/client/src/components/store/editor/sandbox/index.ts
index befc0ac7fc..a47bec39cc 100644
--- a/apps/web/client/src/components/store/editor/sandbox/index.ts
+++ b/apps/web/client/src/components/store/editor/sandbox/index.ts
@@ -7,10 +7,10 @@ import { makeAutoObservable, reaction } from 'mobx';
 import { FileEventBus } from './file-event-bus';
 import { FileSyncManager } from './file-sync';
 import { FileWatcher } from './file-watcher';
-import { formatContent, isSubdirectory, normalizePath } from './helpers';
+import { formatContent, normalizePath } from './helpers';
 import { TemplateNodeMapper } from './mapping';
 import { SessionManager } from './session';
-
+import { isSubdirectory } from '@onlook/utility';
 export class SandboxManager {
     readonly session: SessionManager = new SessionManager();
     private fileWatcher: FileWatcher | null = null;
diff --git a/apps/web/client/test/sandbox/helpers.test.ts b/apps/web/client/test/sandbox/helpers.test.ts
index 7f2efea779..03d101258b 100644
--- a/apps/web/client/test/sandbox/helpers.test.ts
+++ b/apps/web/client/test/sandbox/helpers.test.ts
@@ -1,6 +1,6 @@
 import { describe, expect, mock, test } from 'bun:test';
 import path from 'path';
-import { isSubdirectory, normalizePath } from '../../src/components/store/editor/sandbox/helpers';
+import { normalizePath } from '../../src/components/store/editor/sandbox/helpers';
 
 // Store original path functions
 const originalIsAbsolute = path.isAbsolute;
@@ -44,63 +44,3 @@ describe('normalizePath', () => {
         path.relative = originalRelative;
     });
 });
-
-describe('isSubdirectory', () => {
-    test('should return true if filePath is a subdirectory of directory', () => {
-        expect(isSubdirectory('dir/file.txt', ['dir'])).toBe(true);
-    });
-
-    test('should return false if filePath is not a subdirectory of directory', () => {
-        expect(isSubdirectory('dir/file.txt', ['other'])).toBe(false);
-    });
-
-    test('should return true if filePath is a subdirectory of one of multiple directories', () => {
-        expect(isSubdirectory('dir/file.txt', ['dir', 'other'])).toBe(true);
-    });
-
-    test('should return true if filePath is under a nested directory in list', () => {
-        expect(isSubdirectory('hi/dir/file.txt', ['dir', 'hi'])).toBe(true);
-    });
-
-    test('should return true if filePath matches directory exactly', () => {
-        expect(isSubdirectory('dir', ['dir'])).toBe(true);
-    });
-
-    test('should return true if filePath matches directory with trailing slash', () => {
-        expect(isSubdirectory('dir/', ['dir'])).toBe(true);
-    });
-
-    test('should return false if filePath is only a prefix match', () => {
-        expect(isSubdirectory('dir2/file.txt', ['dir'])).toBe(false);
-    });
-
-    test('should return true if absolute paths are used', () => {
-        expect(isSubdirectory('/home/user/dir/file.txt', ['/home/user/dir'])).toBe(true);
-    });
-
-    test('should return false if absolute paths mismatch', () => {
-        expect(isSubdirectory('/home/user/other/file.txt', ['/home/user/dir'])).toBe(false);
-    });
-
-    test('should handle mixed separators', () => {
-        expect(isSubdirectory('dir\\file.txt', ['dir'])).toBe(true);
-    });
-
-    test('should return false for empty directories list', () => {
-        expect(isSubdirectory('dir/file.txt', [])).toBe(false);
-    });
-
-    test('should handle directory paths with trailing slashes', () => {
-        expect(isSubdirectory('dir/file.txt', ['dir/'])).toBe(true);
-    });
-
-    test('should handle complex nested paths', () => {
-        expect(isSubdirectory('dir/subdir/file.txt', ['dir'])).toBe(true);
-        expect(isSubdirectory('dir/subdir/file.txt', ['dir/subdir'])).toBe(true);
-        expect(isSubdirectory('dir/subdir/file.txt', ['dir/subdir/'])).toBe(true);
-    });
-
-    test('should be case-sensitive on case-sensitive systems', () => {
-        expect(isSubdirectory('Dir/file.txt', ['dir'])).toBe(false);
-    });
-});
diff --git a/apps/web/client/test/sandbox/subdirectory.test.ts b/apps/web/client/test/sandbox/subdirectory.test.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/apps/web/preload/dist/index.js b/apps/web/preload/dist/index.js
index 7164e5d467..8c9d489105 100644
--- a/apps/web/preload/dist/index.js
+++ b/apps/web/preload/dist/index.js
@@ -2,6 +2,7 @@ var __create = Object.create;
 var __getProtoOf = Object.getPrototypeOf;
 var __defProp = Object.defineProperty;
 var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
 var __hasOwnProp = Object.prototype.hasOwnProperty;
 var __toESM = (mod, isNodeMode, target) => {
   target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -14,6 +15,20 @@ var __toESM = (mod, isNodeMode, target) => {
       });
   return to;
 };
+var __moduleCache = /* @__PURE__ */ new WeakMap;
+var __toCommonJS = (from) => {
+  var entry = __moduleCache.get(from), desc;
+  if (entry)
+    return entry;
+  entry = __defProp({}, "__esModule", { value: true });
+  if (from && typeof from === "object" || typeof from === "function")
+    __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
+      get: () => from[key],
+      enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
+    }));
+  __moduleCache.set(from, entry);
+  return entry;
+};
 var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
 var __export = (target, all) => {
   for (var name in all)
@@ -24,6 +39,7 @@ var __export = (target, all) => {
       set: (newValue) => all[name] = () => newValue
     });
 };
+var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
 
 // ../../../node_modules/lodash/isObject.js
 var require_isObject = __commonJS((exports, module) => {
@@ -143474,6 +143490,231 @@ Using plugins:`);
   });
 });
 
+// node:path
+var exports_path = {};
+__export(exports_path, {
+  win32: () => y,
+  toNamespacedPath: () => U,
+  sep: () => I,
+  resolve: () => B,
+  relative: () => Q,
+  posix: () => g,
+  parse: () => $,
+  normalize: () => G,
+  join: () => K,
+  isAbsolute: () => H,
+  format: () => Z,
+  extname: () => Y,
+  dirname: () => V,
+  delimiter: () => O,
+  default: () => q,
+  basename: () => X
+});
+var L, h, D, T, _, E, R = (s, e) => () => (e || s((e = { exports: {} }).exports, e), e.exports), N = (s, e, r, t) => {
+  if (e && typeof e == "object" || typeof e == "function")
+    for (let i of T(e))
+      !E.call(s, i) && i !== r && h(s, i, { get: () => e[i], enumerable: !(t = D(e, i)) || t.enumerable });
+  return s;
+}, j = (s, e, r) => (r = s != null ? L(_(s)) : {}, N(e || !s || !s.__esModule ? h(r, "default", { value: s, enumerable: true }) : r, s)), k, x, u, J, P = function(s) {
+  return s;
+}, S = function() {
+  throw new Error("Not implemented");
+}, g, y, q, B, G, H, K, Q, U, V, X, Y, Z, $, I, O;
+var init_path = __esm(() => {
+  L = Object.create;
+  h = Object.defineProperty;
+  D = Object.getOwnPropertyDescriptor;
+  T = Object.getOwnPropertyNames;
+  _ = Object.getPrototypeOf;
+  E = Object.prototype.hasOwnProperty;
+  k = R((W, w) => {
+    function v(s) {
+      if (typeof s != "string")
+        throw new TypeError("Path must be a string. Received " + JSON.stringify(s));
+    }
+    function C(s, e) {
+      for (var r = "", t = 0, i = -1, a = 0, n, l = 0;l <= s.length; ++l) {
+        if (l < s.length)
+          n = s.charCodeAt(l);
+        else {
+          if (n === 47)
+            break;
+          n = 47;
+        }
+        if (n === 47) {
+          if (!(i === l - 1 || a === 1))
+            if (i !== l - 1 && a === 2) {
+              if (r.length < 2 || t !== 2 || r.charCodeAt(r.length - 1) !== 46 || r.charCodeAt(r.length - 2) !== 46) {
+                if (r.length > 2) {
+                  var f = r.lastIndexOf("/");
+                  if (f !== r.length - 1) {
+                    f === -1 ? (r = "", t = 0) : (r = r.slice(0, f), t = r.length - 1 - r.lastIndexOf("/")), i = l, a = 0;
+                    continue;
+                  }
+                } else if (r.length === 2 || r.length === 1) {
+                  r = "", t = 0, i = l, a = 0;
+                  continue;
+                }
+              }
+              e && (r.length > 0 ? r += "/.." : r = "..", t = 2);
+            } else
+              r.length > 0 ? r += "/" + s.slice(i + 1, l) : r = s.slice(i + 1, l), t = l - i - 1;
+          i = l, a = 0;
+        } else
+          n === 46 && a !== -1 ? ++a : a = -1;
+      }
+      return r;
+    }
+    function F(s, e) {
+      var r = e.dir || e.root, t = e.base || (e.name || "") + (e.ext || "");
+      return r ? r === e.root ? r + t : r + s + t : t;
+    }
+    var m = { resolve: function() {
+      for (var e = "", r = false, t, i = arguments.length - 1;i >= -1 && !r; i--) {
+        var a;
+        i >= 0 ? a = arguments[i] : (t === undefined && (t = process.cwd()), a = t), v(a), a.length !== 0 && (e = a + "/" + e, r = a.charCodeAt(0) === 47);
+      }
+      return e = C(e, !r), r ? e.length > 0 ? "/" + e : "/" : e.length > 0 ? e : ".";
+    }, normalize: function(e) {
+      if (v(e), e.length === 0)
+        return ".";
+      var r = e.charCodeAt(0) === 47, t = e.charCodeAt(e.length - 1) === 47;
+      return e = C(e, !r), e.length === 0 && !r && (e = "."), e.length > 0 && t && (e += "/"), r ? "/" + e : e;
+    }, isAbsolute: function(e) {
+      return v(e), e.length > 0 && e.charCodeAt(0) === 47;
+    }, join: function() {
+      if (arguments.length === 0)
+        return ".";
+      for (var e, r = 0;r < arguments.length; ++r) {
+        var t = arguments[r];
+        v(t), t.length > 0 && (e === undefined ? e = t : e += "/" + t);
+      }
+      return e === undefined ? "." : m.normalize(e);
+    }, relative: function(e, r) {
+      if (v(e), v(r), e === r || (e = m.resolve(e), r = m.resolve(r), e === r))
+        return "";
+      for (var t = 1;t < e.length && e.charCodeAt(t) === 47; ++t)
+        ;
+      for (var i = e.length, a = i - t, n = 1;n < r.length && r.charCodeAt(n) === 47; ++n)
+        ;
+      for (var l = r.length, f = l - n, c = a < f ? a : f, d = -1, o = 0;o <= c; ++o) {
+        if (o === c) {
+          if (f > c) {
+            if (r.charCodeAt(n + o) === 47)
+              return r.slice(n + o + 1);
+            if (o === 0)
+              return r.slice(n + o);
+          } else
+            a > c && (e.charCodeAt(t + o) === 47 ? d = o : o === 0 && (d = 0));
+          break;
+        }
+        var A = e.charCodeAt(t + o), z2 = r.charCodeAt(n + o);
+        if (A !== z2)
+          break;
+        A === 47 && (d = o);
+      }
+      var b = "";
+      for (o = t + d + 1;o <= i; ++o)
+        (o === i || e.charCodeAt(o) === 47) && (b.length === 0 ? b += ".." : b += "/..");
+      return b.length > 0 ? b + r.slice(n + d) : (n += d, r.charCodeAt(n) === 47 && ++n, r.slice(n));
+    }, _makeLong: function(e) {
+      return e;
+    }, dirname: function(e) {
+      if (v(e), e.length === 0)
+        return ".";
+      for (var r = e.charCodeAt(0), t = r === 47, i = -1, a = true, n = e.length - 1;n >= 1; --n)
+        if (r = e.charCodeAt(n), r === 47) {
+          if (!a) {
+            i = n;
+            break;
+          }
+        } else
+          a = false;
+      return i === -1 ? t ? "/" : "." : t && i === 1 ? "//" : e.slice(0, i);
+    }, basename: function(e, r) {
+      if (r !== undefined && typeof r != "string")
+        throw new TypeError('"ext" argument must be a string');
+      v(e);
+      var t = 0, i = -1, a = true, n;
+      if (r !== undefined && r.length > 0 && r.length <= e.length) {
+        if (r.length === e.length && r === e)
+          return "";
+        var l = r.length - 1, f = -1;
+        for (n = e.length - 1;n >= 0; --n) {
+          var c = e.charCodeAt(n);
+          if (c === 47) {
+            if (!a) {
+              t = n + 1;
+              break;
+            }
+          } else
+            f === -1 && (a = false, f = n + 1), l >= 0 && (c === r.charCodeAt(l) ? --l === -1 && (i = n) : (l = -1, i = f));
+        }
+        return t === i ? i = f : i === -1 && (i = e.length), e.slice(t, i);
+      } else {
+        for (n = e.length - 1;n >= 0; --n)
+          if (e.charCodeAt(n) === 47) {
+            if (!a) {
+              t = n + 1;
+              break;
+            }
+          } else
+            i === -1 && (a = false, i = n + 1);
+        return i === -1 ? "" : e.slice(t, i);
+      }
+    }, extname: function(e) {
+      v(e);
+      for (var r = -1, t = 0, i = -1, a = true, n = 0, l = e.length - 1;l >= 0; --l) {
+        var f = e.charCodeAt(l);
+        if (f === 47) {
+          if (!a) {
+            t = l + 1;
+            break;
+          }
+          continue;
+        }
+        i === -1 && (a = false, i = l + 1), f === 46 ? r === -1 ? r = l : n !== 1 && (n = 1) : r !== -1 && (n = -1);
+      }
+      return r === -1 || i === -1 || n === 0 || n === 1 && r === i - 1 && r === t + 1 ? "" : e.slice(r, i);
+    }, format: function(e) {
+      if (e === null || typeof e != "object")
+        throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof e);
+      return F("/", e);
+    }, parse: function(e) {
+      v(e);
+      var r = { root: "", dir: "", base: "", ext: "", name: "" };
+      if (e.length === 0)
+        return r;
+      var t = e.charCodeAt(0), i = t === 47, a;
+      i ? (r.root = "/", a = 1) : a = 0;
+      for (var n = -1, l = 0, f = -1, c = true, d = e.length - 1, o = 0;d >= a; --d) {
+        if (t = e.charCodeAt(d), t === 47) {
+          if (!c) {
+            l = d + 1;
+            break;
+          }
+          continue;
+        }
+        f === -1 && (c = false, f = d + 1), t === 46 ? n === -1 ? n = d : o !== 1 && (o = 1) : n !== -1 && (o = -1);
+      }
+      return n === -1 || f === -1 || o === 0 || o === 1 && n === f - 1 && n === l + 1 ? f !== -1 && (l === 0 && i ? r.base = r.name = e.slice(1, f) : r.base = r.name = e.slice(l, f)) : (l === 0 && i ? (r.name = e.slice(1, n), r.base = e.slice(1, f)) : (r.name = e.slice(l, n), r.base = e.slice(l, f)), r.ext = e.slice(n, f)), l > 0 ? r.dir = e.slice(0, l - 1) : i && (r.dir = "/"), r;
+    }, sep: "/", delimiter: ":", win32: null, posix: null };
+    m.posix = m;
+    w.exports = m;
+  });
+  x = j(k());
+  u = x;
+  J = x;
+  u.parse ??= S;
+  J.parse ??= S;
+  g = { resolve: u.resolve.bind(u), normalize: u.normalize.bind(u), isAbsolute: u.isAbsolute.bind(u), join: u.join.bind(u), relative: u.relative.bind(u), toNamespacedPath: P, dirname: u.dirname.bind(u), basename: u.basename.bind(u), extname: u.extname.bind(u), format: u.format.bind(u), parse: u.parse.bind(u), sep: "/", delimiter: ":", win32: undefined, posix: undefined, _makeLong: P };
+  y = { sep: "\\", delimiter: ";", win32: undefined, ...g, posix: g };
+  g.win32 = y.win32 = y;
+  g.posix = g;
+  q = g;
+  ({ resolve: B, normalize: G, isAbsolute: H, join: K, relative: Q, toNamespacedPath: U, dirname: V, basename: X, extname: Y, format: Z, parse: $, sep: I, delimiter: O } = g);
+});
+
 // ../../../node_modules/lodash/lodash.js
 var require_lodash = __commonJS((exports, module) => {
   (function() {
@@ -148890,6 +149131,76 @@ __p += '`;
   }).call(exports);
 });
 
+// ../../../node_modules/is-windows/index.js
+var require_is_windows = __commonJS((exports, module) => {
+  /*!
+   * is-windows <https://github.com/jonschlinkert/is-windows>
+   *
+   * Copyright © 2015-2018, Jon Schlinkert.
+   * Released under the MIT License.
+   */
+  (function(factory) {
+    if (exports && typeof exports === "object" && typeof module !== "undefined") {
+      module.exports = factory();
+    } else if (typeof define === "function" && define.amd) {
+      define([], factory);
+    } else if (typeof window !== "undefined") {
+      window.isWindows = factory();
+    } else if (typeof global !== "undefined") {
+      global.isWindows = factory();
+    } else if (typeof self !== "undefined") {
+      self.isWindows = factory();
+    } else {
+      this.isWindows = factory();
+    }
+  })(function() {
+    return function isWindows() {
+      return process && (process.platform === "win32" || /^(msys|cygwin)$/.test(process.env.OSTYPE));
+    };
+  });
+});
+
+// ../../../node_modules/better-path-resolve/index.js
+var require_better_path_resolve = __commonJS((exports, module) => {
+  var path = (init_path(), __toCommonJS(exports_path));
+  var isWindows = require_is_windows();
+  module.exports = isWindows() ? winResolve : path.resolve;
+  function winResolve(p) {
+    if (arguments.length === 0)
+      return path.resolve();
+    if (typeof p !== "string") {
+      return path.resolve(p);
+    }
+    if (p[1] === ":") {
+      const cc = p[0].charCodeAt();
+      if (cc < 65 || cc > 90) {
+        p = `${p[0].toUpperCase()}${p.substr(1)}`;
+      }
+    }
+    if (p.endsWith(":")) {
+      return p;
+    }
+    return path.resolve(p);
+  }
+});
+
+// ../../../node_modules/is-subdir/index.js
+var require_is_subdir = __commonJS((exports, module) => {
+  var betterPathResolve = require_better_path_resolve();
+  var path = (init_path(), __toCommonJS(exports_path));
+  function isSubdir(parentDir, subdir) {
+    const rParent = `${betterPathResolve(parentDir)}${path.sep}`;
+    const rDir = `${betterPathResolve(subdir)}${path.sep}`;
+    return rDir.startsWith(rParent);
+  }
+  isSubdir.strict = function isSubdirStrict(parentDir, subdir) {
+    const rParent = `${betterPathResolve(parentDir)}${path.sep}`;
+    const rDir = `${betterPathResolve(subdir)}${path.sep}`;
+    return rDir !== rParent && rDir.startsWith(rParent);
+  };
+  module.exports = isSubdir;
+});
+
 // ../../../node_modules/source-map-js/lib/base64.js
 var require_base64 = __commonJS((exports) => {
   var intToCharMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");
@@ -149054,27 +149365,27 @@ var require_util = __commonJS((exports) => {
     };
   }
   var normalize = lruMemoize(function normalize(aPath) {
-    var path = aPath;
+    var path2 = aPath;
     var url = urlParse(aPath);
     if (url) {
       if (!url.path) {
         return aPath;
       }
-      path = url.path;
+      path2 = url.path;
     }
-    var isAbsolute = exports.isAbsolute(path);
+    var isAbsolute = exports.isAbsolute(path2);
     var parts = [];
     var start = 0;
     var i2 = 0;
     while (true) {
       start = i2;
-      i2 = path.indexOf("/", start);
+      i2 = path2.indexOf("/", start);
       if (i2 === -1) {
-        parts.push(path.slice(start));
+        parts.push(path2.slice(start));
         break;
       } else {
-        parts.push(path.slice(start, i2));
-        while (i2 < path.length && path[i2] === "/") {
+        parts.push(path2.slice(start, i2));
+        while (i2 < path2.length && path2[i2] === "/") {
           i2++;
         }
       }
@@ -149095,15 +149406,15 @@ var require_util = __commonJS((exports) => {
         }
       }
     }
-    path = parts.join("/");
-    if (path === "") {
-      path = isAbsolute ? "/" : ".";
+    path2 = parts.join("/");
+    if (path2 === "") {
+      path2 = isAbsolute ? "/" : ".";
     }
     if (url) {
-      url.path = path;
+      url.path = path2;
       return urlGenerate(url);
     }
-    return path;
+    return path2;
   });
   exports.normalize = normalize;
   function join(aRoot, aPath) {
@@ -154569,211 +154880,8 @@ var { parse } = import_standalone.packages.parser;
 var { generate } = import_standalone.packages.generator;
 var traverse = import_standalone.packages.traverse.default;
 var types = import_standalone.packages.types;
-// node:path
-var L = Object.create;
-var h = Object.defineProperty;
-var D = Object.getOwnPropertyDescriptor;
-var T = Object.getOwnPropertyNames;
-var _ = Object.getPrototypeOf;
-var E = Object.prototype.hasOwnProperty;
-var R = (s, e) => () => (e || s((e = { exports: {} }).exports, e), e.exports);
-var N = (s, e, r, t) => {
-  if (e && typeof e == "object" || typeof e == "function")
-    for (let i of T(e))
-      !E.call(s, i) && i !== r && h(s, i, { get: () => e[i], enumerable: !(t = D(e, i)) || t.enumerable });
-  return s;
-};
-var j = (s, e, r) => (r = s != null ? L(_(s)) : {}, N(e || !s || !s.__esModule ? h(r, "default", { value: s, enumerable: true }) : r, s));
-var k = R((W, w) => {
-  function v(s) {
-    if (typeof s != "string")
-      throw new TypeError("Path must be a string. Received " + JSON.stringify(s));
-  }
-  function C(s, e) {
-    for (var r = "", t = 0, i = -1, a = 0, n, l = 0;l <= s.length; ++l) {
-      if (l < s.length)
-        n = s.charCodeAt(l);
-      else {
-        if (n === 47)
-          break;
-        n = 47;
-      }
-      if (n === 47) {
-        if (!(i === l - 1 || a === 1))
-          if (i !== l - 1 && a === 2) {
-            if (r.length < 2 || t !== 2 || r.charCodeAt(r.length - 1) !== 46 || r.charCodeAt(r.length - 2) !== 46) {
-              if (r.length > 2) {
-                var f = r.lastIndexOf("/");
-                if (f !== r.length - 1) {
-                  f === -1 ? (r = "", t = 0) : (r = r.slice(0, f), t = r.length - 1 - r.lastIndexOf("/")), i = l, a = 0;
-                  continue;
-                }
-              } else if (r.length === 2 || r.length === 1) {
-                r = "", t = 0, i = l, a = 0;
-                continue;
-              }
-            }
-            e && (r.length > 0 ? r += "/.." : r = "..", t = 2);
-          } else
-            r.length > 0 ? r += "/" + s.slice(i + 1, l) : r = s.slice(i + 1, l), t = l - i - 1;
-        i = l, a = 0;
-      } else
-        n === 46 && a !== -1 ? ++a : a = -1;
-    }
-    return r;
-  }
-  function F(s, e) {
-    var r = e.dir || e.root, t = e.base || (e.name || "") + (e.ext || "");
-    return r ? r === e.root ? r + t : r + s + t : t;
-  }
-  var m = { resolve: function() {
-    for (var e = "", r = false, t, i = arguments.length - 1;i >= -1 && !r; i--) {
-      var a;
-      i >= 0 ? a = arguments[i] : (t === undefined && (t = process.cwd()), a = t), v(a), a.length !== 0 && (e = a + "/" + e, r = a.charCodeAt(0) === 47);
-    }
-    return e = C(e, !r), r ? e.length > 0 ? "/" + e : "/" : e.length > 0 ? e : ".";
-  }, normalize: function(e) {
-    if (v(e), e.length === 0)
-      return ".";
-    var r = e.charCodeAt(0) === 47, t = e.charCodeAt(e.length - 1) === 47;
-    return e = C(e, !r), e.length === 0 && !r && (e = "."), e.length > 0 && t && (e += "/"), r ? "/" + e : e;
-  }, isAbsolute: function(e) {
-    return v(e), e.length > 0 && e.charCodeAt(0) === 47;
-  }, join: function() {
-    if (arguments.length === 0)
-      return ".";
-    for (var e, r = 0;r < arguments.length; ++r) {
-      var t = arguments[r];
-      v(t), t.length > 0 && (e === undefined ? e = t : e += "/" + t);
-    }
-    return e === undefined ? "." : m.normalize(e);
-  }, relative: function(e, r) {
-    if (v(e), v(r), e === r || (e = m.resolve(e), r = m.resolve(r), e === r))
-      return "";
-    for (var t = 1;t < e.length && e.charCodeAt(t) === 47; ++t)
-      ;
-    for (var i = e.length, a = i - t, n = 1;n < r.length && r.charCodeAt(n) === 47; ++n)
-      ;
-    for (var l = r.length, f = l - n, c = a < f ? a : f, d = -1, o = 0;o <= c; ++o) {
-      if (o === c) {
-        if (f > c) {
-          if (r.charCodeAt(n + o) === 47)
-            return r.slice(n + o + 1);
-          if (o === 0)
-            return r.slice(n + o);
-        } else
-          a > c && (e.charCodeAt(t + o) === 47 ? d = o : o === 0 && (d = 0));
-        break;
-      }
-      var A = e.charCodeAt(t + o), z2 = r.charCodeAt(n + o);
-      if (A !== z2)
-        break;
-      A === 47 && (d = o);
-    }
-    var b = "";
-    for (o = t + d + 1;o <= i; ++o)
-      (o === i || e.charCodeAt(o) === 47) && (b.length === 0 ? b += ".." : b += "/..");
-    return b.length > 0 ? b + r.slice(n + d) : (n += d, r.charCodeAt(n) === 47 && ++n, r.slice(n));
-  }, _makeLong: function(e) {
-    return e;
-  }, dirname: function(e) {
-    if (v(e), e.length === 0)
-      return ".";
-    for (var r = e.charCodeAt(0), t = r === 47, i = -1, a = true, n = e.length - 1;n >= 1; --n)
-      if (r = e.charCodeAt(n), r === 47) {
-        if (!a) {
-          i = n;
-          break;
-        }
-      } else
-        a = false;
-    return i === -1 ? t ? "/" : "." : t && i === 1 ? "//" : e.slice(0, i);
-  }, basename: function(e, r) {
-    if (r !== undefined && typeof r != "string")
-      throw new TypeError('"ext" argument must be a string');
-    v(e);
-    var t = 0, i = -1, a = true, n;
-    if (r !== undefined && r.length > 0 && r.length <= e.length) {
-      if (r.length === e.length && r === e)
-        return "";
-      var l = r.length - 1, f = -1;
-      for (n = e.length - 1;n >= 0; --n) {
-        var c = e.charCodeAt(n);
-        if (c === 47) {
-          if (!a) {
-            t = n + 1;
-            break;
-          }
-        } else
-          f === -1 && (a = false, f = n + 1), l >= 0 && (c === r.charCodeAt(l) ? --l === -1 && (i = n) : (l = -1, i = f));
-      }
-      return t === i ? i = f : i === -1 && (i = e.length), e.slice(t, i);
-    } else {
-      for (n = e.length - 1;n >= 0; --n)
-        if (e.charCodeAt(n) === 47) {
-          if (!a) {
-            t = n + 1;
-            break;
-          }
-        } else
-          i === -1 && (a = false, i = n + 1);
-      return i === -1 ? "" : e.slice(t, i);
-    }
-  }, extname: function(e) {
-    v(e);
-    for (var r = -1, t = 0, i = -1, a = true, n = 0, l = e.length - 1;l >= 0; --l) {
-      var f = e.charCodeAt(l);
-      if (f === 47) {
-        if (!a) {
-          t = l + 1;
-          break;
-        }
-        continue;
-      }
-      i === -1 && (a = false, i = l + 1), f === 46 ? r === -1 ? r = l : n !== 1 && (n = 1) : r !== -1 && (n = -1);
-    }
-    return r === -1 || i === -1 || n === 0 || n === 1 && r === i - 1 && r === t + 1 ? "" : e.slice(r, i);
-  }, format: function(e) {
-    if (e === null || typeof e != "object")
-      throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof e);
-    return F("/", e);
-  }, parse: function(e) {
-    v(e);
-    var r = { root: "", dir: "", base: "", ext: "", name: "" };
-    if (e.length === 0)
-      return r;
-    var t = e.charCodeAt(0), i = t === 47, a;
-    i ? (r.root = "/", a = 1) : a = 0;
-    for (var n = -1, l = 0, f = -1, c = true, d = e.length - 1, o = 0;d >= a; --d) {
-      if (t = e.charCodeAt(d), t === 47) {
-        if (!c) {
-          l = d + 1;
-          break;
-        }
-        continue;
-      }
-      f === -1 && (c = false, f = d + 1), t === 46 ? n === -1 ? n = d : o !== 1 && (o = 1) : n !== -1 && (o = -1);
-    }
-    return n === -1 || f === -1 || o === 0 || o === 1 && n === f - 1 && n === l + 1 ? f !== -1 && (l === 0 && i ? r.base = r.name = e.slice(1, f) : r.base = r.name = e.slice(l, f)) : (l === 0 && i ? (r.name = e.slice(1, n), r.base = e.slice(1, f)) : (r.name = e.slice(l, n), r.base = e.slice(l, f)), r.ext = e.slice(n, f)), l > 0 ? r.dir = e.slice(0, l - 1) : i && (r.dir = "/"), r;
-  }, sep: "/", delimiter: ":", win32: null, posix: null };
-  m.posix = m;
-  w.exports = m;
-});
-var x = j(k());
-var u = x;
-var J = x;
-var P = function(s) {
-  return s;
-};
-var S = function() {
-  throw new Error("Not implemented");
-};
-u.parse ??= S;
-J.parse ??= S;
-var g = { resolve: u.resolve.bind(u), normalize: u.normalize.bind(u), isAbsolute: u.isAbsolute.bind(u), join: u.join.bind(u), relative: u.relative.bind(u), toNamespacedPath: P, dirname: u.dirname.bind(u), basename: u.basename.bind(u), extname: u.extname.bind(u), format: u.format.bind(u), parse: u.parse.bind(u), sep: "/", delimiter: ":", win32: undefined, posix: undefined, _makeLong: P };
-var y = { sep: "\\", delimiter: ";", win32: undefined, ...g, posix: g };
-g.win32 = y.win32 = y;
-g.posix = g;
+// ../../../packages/parser/src/code-edit/image.ts
+init_path();
 // ../../../packages/models/src/chat/suggestion.ts
 var ChatSuggestionSchema = z.object({
   title: z.string().describe("The display title of the suggestion. This will be shown to the user. Keep it concise but descriptive."),
@@ -155070,13 +155178,13 @@ var t2 = { get exports() {
         (p > 14000 || m > 26697) && h2 - l > 100 && (u2 < l && (d[p] = l - u2, p += 2, u2 = l), c = UZIP.F._writeBlock(l == h2 - 1 || u2 == h2 ? 1 : 0, d, p, v, e3, w, l - w, t3, c), p = m = v = 0, w = l);
         var F = 0;
         l < h2 - 2 && (F = UZIP.F._bestMatch(e3, l, g2, b, Math.min(o[2], h2 - l), o[3]));
-        var _2 = F >>> 16, B = 65535 & F;
+        var _2 = F >>> 16, B2 = 65535 & F;
         if (F != 0) {
-          B = 65535 & F;
-          var U = s(_2 = F >>> 16, a.of0);
-          a.lhst[257 + U]++;
-          var C = s(B, a.df0);
-          a.dhst[C]++, v += a.exb[U] + a.dxb[C], d[p] = _2 << 23 | l - u2, d[p + 1] = B << 16 | U << 8 | C, p += 2, u2 = l + _2;
+          B2 = 65535 & F;
+          var U2 = s(_2 = F >>> 16, a.of0);
+          a.lhst[257 + U2]++;
+          var C = s(B2, a.df0);
+          a.dhst[C]++, v += a.exb[U2] + a.dxb[C], d[p] = _2 << 23 | l - u2, d[p + 1] = B2 << 16 | U2 << 8 | C, p += 2, u2 = l + _2;
         } else
           a.lhst[e3[l]]++;
         m++;
@@ -155119,31 +155227,31 @@ var t2 = { get exports() {
     v.lhst[256]++, u2 = (c = UZIP.F.getTrees())[0], h2 = c[1], d = c[2], A = c[3], g2 = c[4], p = c[5], m = c[6], w = c[7];
     var E2 = 32 + ((l + 3 & 7) == 0 ? 0 : 8 - (l + 3 & 7)) + (s << 3), F = i2 + UZIP.F.contSize(v.fltree, v.lhst) + UZIP.F.contSize(v.fdtree, v.dhst), _2 = i2 + UZIP.F.contSize(v.ltree, v.lhst) + UZIP.F.contSize(v.dtree, v.dhst);
     _2 += 14 + 3 * p + UZIP.F.contSize(v.itree, v.ihst) + (2 * v.ihst[16] + 3 * v.ihst[17] + 7 * v.ihst[18]);
-    for (var B = 0;B < 286; B++)
-      v.lhst[B] = 0;
-    for (B = 0;B < 30; B++)
-      v.dhst[B] = 0;
-    for (B = 0;B < 19; B++)
-      v.ihst[B] = 0;
-    var U = E2 < F && E2 < _2 ? 0 : F < _2 ? 1 : 2;
-    if (b(f, l, e3), b(f, l + 1, U), l += 3, U == 0) {
+    for (var B2 = 0;B2 < 286; B2++)
+      v.lhst[B2] = 0;
+    for (B2 = 0;B2 < 30; B2++)
+      v.dhst[B2] = 0;
+    for (B2 = 0;B2 < 19; B2++)
+      v.ihst[B2] = 0;
+    var U2 = E2 < F && E2 < _2 ? 0 : F < _2 ? 1 : 2;
+    if (b(f, l, e3), b(f, l + 1, U2), l += 3, U2 == 0) {
       for (;(7 & l) != 0; )
         l++;
       l = UZIP.F._copyExact(o, a, s, f, l);
     } else {
-      var C, I;
-      if (U == 1 && (C = v.fltree, I = v.fdtree), U == 2) {
-        UZIP.F.makeCodes(v.ltree, u2), UZIP.F.revCodes(v.ltree, u2), UZIP.F.makeCodes(v.dtree, h2), UZIP.F.revCodes(v.dtree, h2), UZIP.F.makeCodes(v.itree, d), UZIP.F.revCodes(v.itree, d), C = v.ltree, I = v.dtree, y2(f, l, A - 257), y2(f, l += 5, g2 - 1), y2(f, l += 5, p - 4), l += 4;
-        for (var Q = 0;Q < p; Q++)
-          y2(f, l + 3 * Q, v.itree[1 + (v.ordr[Q] << 1)]);
+      var C, I2;
+      if (U2 == 1 && (C = v.fltree, I2 = v.fdtree), U2 == 2) {
+        UZIP.F.makeCodes(v.ltree, u2), UZIP.F.revCodes(v.ltree, u2), UZIP.F.makeCodes(v.dtree, h2), UZIP.F.revCodes(v.dtree, h2), UZIP.F.makeCodes(v.itree, d), UZIP.F.revCodes(v.itree, d), C = v.ltree, I2 = v.dtree, y2(f, l, A - 257), y2(f, l += 5, g2 - 1), y2(f, l += 5, p - 4), l += 4;
+        for (var Q2 = 0;Q2 < p; Q2++)
+          y2(f, l + 3 * Q2, v.itree[1 + (v.ordr[Q2] << 1)]);
         l += 3 * p, l = UZIP.F._codeTiny(m, v.itree, f, l), l = UZIP.F._codeTiny(w, v.itree, f, l);
       }
       for (var M = a, x2 = 0;x2 < r2; x2 += 2) {
         for (var S2 = t3[x2], R2 = S2 >>> 23, T2 = M + (8388607 & S2);M < T2; )
           l = UZIP.F._writeLit(o[M++], C, f, l);
         if (R2 != 0) {
-          var O = t3[x2 + 1], P2 = O >> 16, H = O >> 8 & 255, L2 = 255 & O;
-          y2(f, l = UZIP.F._writeLit(257 + H, C, f, l), R2 - v.of0[H]), l += v.exb[H], b(f, l = UZIP.F._writeLit(L2, I, f, l), P2 - v.df0[L2]), l += v.dxb[L2], M += R2;
+          var O2 = t3[x2 + 1], P2 = O2 >> 16, H2 = O2 >> 8 & 255, L2 = 255 & O2;
+          y2(f, l = UZIP.F._writeLit(257 + H2, C, f, l), R2 - v.of0[H2]), l += v.exb[H2], b(f, l = UZIP.F._writeLit(L2, I2, f, l), P2 - v.df0[L2]), l += v.dxb[L2], M += R2;
         }
       }
       l = UZIP.F._writeLit(256, C, f, l);
@@ -155255,21 +155363,21 @@ var t2 = { get exports() {
           m = a(e3, F, 5) + 257, w = a(e3, F + 5, 5) + 1, v = a(e3, F + 10, 4) + 4, F += 14;
           for (var _2 = 0;_2 < 38; _2 += 2)
             u2.itree[_2] = 0, u2.itree[_2 + 1] = 0;
-          var B = 1;
+          var B2 = 1;
           for (_2 = 0;_2 < v; _2++) {
-            var U = a(e3, F + 3 * _2, 3);
-            u2.itree[1 + (u2.ordr[_2] << 1)] = U, U > B && (B = U);
+            var U2 = a(e3, F + 3 * _2, 3);
+            u2.itree[1 + (u2.ordr[_2] << 1)] = U2, U2 > B2 && (B2 = U2);
           }
-          F += 3 * v, f(u2.itree, B), l(u2.itree, B, u2.imap), d = u2.lmap, A = u2.dmap, F = s(u2.imap, (1 << B) - 1, m + w, e3, F, u2.ttree);
+          F += 3 * v, f(u2.itree, B2), l(u2.itree, B2, u2.imap), d = u2.lmap, A = u2.dmap, F = s(u2.imap, (1 << B2) - 1, m + w, e3, F, u2.ttree);
           var C = i2._copyOut(u2.ttree, 0, m, u2.ltree);
           b = (1 << C) - 1;
-          var I = i2._copyOut(u2.ttree, m, w, u2.dtree);
-          y2 = (1 << I) - 1, f(u2.ltree, C), l(u2.ltree, C, d), f(u2.dtree, I), l(u2.dtree, I, A);
+          var I2 = i2._copyOut(u2.ttree, m, w, u2.dtree);
+          y2 = (1 << I2) - 1, f(u2.ltree, C), l(u2.ltree, C, d), f(u2.dtree, I2), l(u2.dtree, I2, A);
         }
         for (;; ) {
-          var Q = d[c(e3, F) & b];
-          F += 15 & Q;
-          var M = Q >>> 4;
+          var Q2 = d[c(e3, F) & b];
+          F += 15 & Q2;
+          var M = Q2 >>> 4;
           if (M >>> 8 == 0)
             t3[E2++] = M;
           else {
@@ -155282,16 +155390,16 @@ var t2 = { get exports() {
             }
             var R2 = A[c(e3, F) & y2];
             F += 15 & R2;
-            var T2 = R2 >>> 4, O = u2.ddef[T2], P2 = (O >>> 4) + o(e3, F, 15 & O);
-            for (F += 15 & O, h2 && (t3 = UZIP.F._check(t3, E2 + (1 << 17)));E2 < x2; )
+            var T2 = R2 >>> 4, O2 = u2.ddef[T2], P2 = (O2 >>> 4) + o(e3, F, 15 & O2);
+            for (F += 15 & O2, h2 && (t3 = UZIP.F._check(t3, E2 + (1 << 17)));E2 < x2; )
               t3[E2] = t3[E2++ - P2], t3[E2] = t3[E2++ - P2], t3[E2] = t3[E2++ - P2], t3[E2] = t3[E2++ - P2];
             E2 = x2;
           }
         }
       } else {
         (7 & F) != 0 && (F += 8 - (7 & F));
-        var H = 4 + (F >>> 3), L2 = e3[H - 4] | e3[H - 3] << 8;
-        h2 && (t3 = UZIP.F._check(t3, E2 + L2)), t3.set(new r2(e3.buffer, e3.byteOffset + H, L2), E2), F = H + L2 << 3, E2 += L2;
+        var H2 = 4 + (F >>> 3), L2 = e3[H2 - 4] | e3[H2 - 3] << 8;
+        h2 && (t3 = UZIP.F._check(t3, E2 + L2)), t3.set(new r2(e3.buffer, e3.byteOffset + H2, L2), E2), F = H2 + L2 << 3, E2 += L2;
       }
     return t3.length == E2 ? t3 : t3.slice(0, E2);
   }, UZIP.F._check = function(e3, t3) {
@@ -155492,29 +155600,29 @@ var UPNG = function() {
       for (p = o.tabs.tRNS ? o.tabs.tRNS : -1, w = 0;w < i; w++) {
         const e3 = w * f, i2 = w * r;
         if (h2 == 1)
-          for (var B = 0;B < r; B++) {
-            var U = (F = 255 * (t4[e3 + (B >>> 3)] >>> 7 - (7 & B) & 1)) == 255 * p ? 0 : 255;
-            c[i2 + B] = U << 24 | F << 16 | F << 8 | F;
+          for (var B2 = 0;B2 < r; B2++) {
+            var U2 = (F = 255 * (t4[e3 + (B2 >>> 3)] >>> 7 - (7 & B2) & 1)) == 255 * p ? 0 : 255;
+            c[i2 + B2] = U2 << 24 | F << 16 | F << 8 | F;
           }
         else if (h2 == 2)
-          for (B = 0;B < r; B++) {
-            U = (F = 85 * (t4[e3 + (B >>> 2)] >>> 6 - ((3 & B) << 1) & 3)) == 85 * p ? 0 : 255;
-            c[i2 + B] = U << 24 | F << 16 | F << 8 | F;
+          for (B2 = 0;B2 < r; B2++) {
+            U2 = (F = 85 * (t4[e3 + (B2 >>> 2)] >>> 6 - ((3 & B2) << 1) & 3)) == 85 * p ? 0 : 255;
+            c[i2 + B2] = U2 << 24 | F << 16 | F << 8 | F;
           }
         else if (h2 == 4)
-          for (B = 0;B < r; B++) {
-            U = (F = 17 * (t4[e3 + (B >>> 1)] >>> 4 - ((1 & B) << 2) & 15)) == 17 * p ? 0 : 255;
-            c[i2 + B] = U << 24 | F << 16 | F << 8 | F;
+          for (B2 = 0;B2 < r; B2++) {
+            U2 = (F = 17 * (t4[e3 + (B2 >>> 1)] >>> 4 - ((1 & B2) << 2) & 15)) == 17 * p ? 0 : 255;
+            c[i2 + B2] = U2 << 24 | F << 16 | F << 8 | F;
           }
         else if (h2 == 8)
-          for (B = 0;B < r; B++) {
-            U = (F = t4[e3 + B]) == p ? 0 : 255;
-            c[i2 + B] = U << 24 | F << 16 | F << 8 | F;
+          for (B2 = 0;B2 < r; B2++) {
+            U2 = (F = t4[e3 + B2]) == p ? 0 : 255;
+            c[i2 + B2] = U2 << 24 | F << 16 | F << 8 | F;
           }
         else if (h2 == 16)
-          for (B = 0;B < r; B++) {
-            F = t4[e3 + (B << 1)], U = d(t4, e3 + (B << 1)) == p ? 0 : 255;
-            c[i2 + B] = U << 24 | F << 16 | F << 8 | F;
+          for (B2 = 0;B2 < r; B2++) {
+            F = t4[e3 + (B2 << 1)], U2 = d(t4, e3 + (B2 << 1)) == p ? 0 : 255;
+            c[i2 + B2] = U2 << 24 | F << 16 | F << 8 | F;
           }
       }
     return l;
@@ -155577,11 +155685,11 @@ var UPNG = function() {
           if (_2 && (r = e3.H.W(r, A + (1 << 17))), f == 1 && (o = F.J, a = F.h, h2 = 511, d = 31), f == 2) {
             l = w(t4, g2, 5) + 257, c = w(t4, g2 + 5, 5) + 1, u2 = w(t4, g2 + 10, 4) + 4, g2 += 14;
             let e4 = 1;
-            for (var B = 0;B < 38; B += 2)
-              F.Q[B] = 0, F.Q[B + 1] = 0;
-            for (B = 0;B < u2; B++) {
-              const r3 = w(t4, g2 + 3 * B, 3);
-              F.Q[1 + (F.X[B] << 1)] = r3, r3 > e4 && (e4 = r3);
+            for (var B2 = 0;B2 < 38; B2 += 2)
+              F.Q[B2] = 0, F.Q[B2 + 1] = 0;
+            for (B2 = 0;B2 < u2; B2++) {
+              const r3 = w(t4, g2 + 3 * B2, 3);
+              F.Q[1 + (F.X[B2] << 1)] = r3, r3 > e4 && (e4 = r3);
             }
             g2 += 3 * u2, b(F.Q, e4), y2(F.Q, e4, F.u), o = F.w, a = F.d, g2 = v(F.u, (1 << e4) - 1, l + c, t4, g2, F.v);
             const r2 = p.V(F.v, 0, l, F.C);
@@ -156100,25 +156208,25 @@ var UPNG = function() {
         E2.push(r3.plte[w].est.rgba);
       let i3 = 0;
       for (w = 0;w < b.length; w++) {
-        const e4 = (B = b[w]).img.length;
+        const e4 = (B2 = b[w]).img.length;
         var _2 = new Uint8Array(r3.inds.buffer, i3 >> 2, e4 >> 2);
         F.push(_2);
         const t6 = new Uint8Array(r3.abuf, i3, e4);
-        h2 && dither(B.img, B.rect.width, B.rect.height, E2, t6, _2), B.img.set(t6), i3 += e4;
+        h2 && dither(B2.img, B2.rect.width, B2.rect.height, E2, t6, _2), B2.img.set(t6), i3 += e4;
       }
     } else
       for (p = 0;p < b.length; p++) {
-        var B = b[p];
-        const e3 = new Uint32Array(B.img.buffer);
-        var U = B.rect.width;
+        var B2 = b[p];
+        const e3 = new Uint32Array(B2.img.buffer);
+        var U2 = B2.rect.width;
         m = e3.length, _2 = new Uint8Array(m);
         F.push(_2);
         for (w = 0;w < m; w++) {
           const t5 = e3[w];
           if (w != 0 && t5 == e3[w - 1])
             _2[w] = _2[w - 1];
-          else if (w > U && t5 == e3[w - U])
-            _2[w] = _2[w - U];
+          else if (w > U2 && t5 == e3[w - U2])
+            _2[w] = _2[w - U2];
           else {
             let e4 = y2[t5];
             if (e4 == null && (y2[t5] = e4 = E2.length, E2.push(t5), E2.length >= 300))
@@ -156130,43 +156238,43 @@ var UPNG = function() {
     const C = E2.length;
     C <= 256 && u2 == 0 && (A = C <= 2 ? 1 : C <= 4 ? 2 : C <= 16 ? 4 : 8, A = Math.max(A, c));
     for (p = 0;p < b.length; p++) {
-      (B = b[p]).rect.x, B.rect.y;
-      U = B.rect.width;
-      const e3 = B.rect.height;
-      let t5 = B.img;
+      (B2 = b[p]).rect.x, B2.rect.y;
+      U2 = B2.rect.width;
+      const e3 = B2.rect.height;
+      let t5 = B2.img;
       new Uint32Array(t5.buffer);
-      let r3 = 4 * U, i3 = 4;
+      let r3 = 4 * U2, i3 = 4;
       if (C <= 256 && u2 == 0) {
-        r3 = Math.ceil(A * U / 8);
-        var I = new Uint8Array(r3 * e3);
+        r3 = Math.ceil(A * U2 / 8);
+        var I2 = new Uint8Array(r3 * e3);
         const o3 = F[p];
         for (let t6 = 0;t6 < e3; t6++) {
           w = t6 * r3;
-          const e4 = t6 * U;
+          const e4 = t6 * U2;
           if (A == 8)
-            for (var Q = 0;Q < U; Q++)
-              I[w + Q] = o3[e4 + Q];
+            for (var Q2 = 0;Q2 < U2; Q2++)
+              I2[w + Q2] = o3[e4 + Q2];
           else if (A == 4)
-            for (Q = 0;Q < U; Q++)
-              I[w + (Q >> 1)] |= o3[e4 + Q] << 4 - 4 * (1 & Q);
+            for (Q2 = 0;Q2 < U2; Q2++)
+              I2[w + (Q2 >> 1)] |= o3[e4 + Q2] << 4 - 4 * (1 & Q2);
           else if (A == 2)
-            for (Q = 0;Q < U; Q++)
-              I[w + (Q >> 2)] |= o3[e4 + Q] << 6 - 2 * (3 & Q);
+            for (Q2 = 0;Q2 < U2; Q2++)
+              I2[w + (Q2 >> 2)] |= o3[e4 + Q2] << 6 - 2 * (3 & Q2);
           else if (A == 1)
-            for (Q = 0;Q < U; Q++)
-              I[w + (Q >> 3)] |= o3[e4 + Q] << 7 - 1 * (7 & Q);
+            for (Q2 = 0;Q2 < U2; Q2++)
+              I2[w + (Q2 >> 3)] |= o3[e4 + Q2] << 7 - 1 * (7 & Q2);
         }
-        t5 = I, d = 3, i3 = 1;
+        t5 = I2, d = 3, i3 = 1;
       } else if (v == 0 && b.length == 1) {
-        I = new Uint8Array(U * e3 * 3);
-        const o3 = U * e3;
+        I2 = new Uint8Array(U2 * e3 * 3);
+        const o3 = U2 * e3;
         for (w = 0;w < o3; w++) {
           const e4 = 3 * w, r4 = 4 * w;
-          I[e4] = t5[r4], I[e4 + 1] = t5[r4 + 1], I[e4 + 2] = t5[r4 + 2];
+          I2[e4] = t5[r4], I2[e4 + 1] = t5[r4 + 1], I2[e4 + 2] = t5[r4 + 2];
         }
-        t5 = I, d = 2, i3 = 3, r3 = 3 * U;
+        t5 = I2, d = 2, i3 = 3, r3 = 3 * U2;
       }
-      B.img = t5, B.bpl = r3, B.bpp = i3;
+      B2.img = t5, B2.bpl = r3, B2.bpp = i3;
     }
     return { ctype: d, depth: A, plte: E2, frames: b };
   }
@@ -156654,7 +156762,7 @@ function followExifOrientation(e2, t3) {
 }
 function compress(e2, t3, r2 = 0) {
   return new Promise(function(i2, o2) {
-    let a2, s2, f2, l, c, u2, h2, d, A, g2, p, m, w, v, b, y2, E2, F, _2, B;
+    let a2, s2, f2, l, c, u2, h2, d, A, g2, p, m, w, v, b, y2, E2, F, _2, B2;
     function incProgress(e3 = 5) {
       if (t3.signal && t3.signal.aborted)
         throw t3.signal.reason;
@@ -156691,7 +156799,7 @@ function compress(e2, t3, r2 = 0) {
                       let $Loop_3 = function() {
                         if (s2-- && (b > f2 || b > w)) {
                           let t4, r7;
-                          return t4 = B ? 0.95 * _2.width : _2.width, r7 = B ? 0.95 * _2.height : _2.height, [E2, F] = getNewCanvasAndCtx(t4, r7), F.drawImage(_2, 0, 0, t4, r7), d *= A === "image/png" ? 0.85 : 0.95, canvasToFile(E2, A, e2.name, e2.lastModified, d).then(function(e3) {
+                          return t4 = B2 ? 0.95 * _2.width : _2.width, r7 = B2 ? 0.95 * _2.height : _2.height, [E2, F] = getNewCanvasAndCtx(t4, r7), F.drawImage(_2, 0, 0, t4, r7), d *= A === "image/png" ? 0.85 : 0.95, canvasToFile(E2, A, e2.name, e2.lastModified, d).then(function(e3) {
                             try {
                               return y2 = e3, cleanupCanvasMemory(_2), _2 = E2, b = y2.size, setProgress(Math.min(99, Math.floor((v - b) / (v - f2) * 100))), $Loop_3;
                             } catch (e4) {
@@ -156706,7 +156814,7 @@ function compress(e2, t3, r2 = 0) {
                       if (g2 = r6, incProgress(), p = g2.size > f2, m = g2.size > e2.size, !p && !m)
                         return setProgress(100), i2(g2);
                       var a3;
-                      return w = e2.size, v = g2.size, b = v, _2 = h2, B = !t3.alwaysKeepResolution && p, (a3 = function(e3) {
+                      return w = e2.size, v = g2.size, b = v, _2 = h2, B2 = !t3.alwaysKeepResolution && p, (a3 = function(e3) {
                         for (;e3; ) {
                           if (e3.then)
                             return void e3.then(a3, o2);
@@ -156842,6 +156950,9 @@ function imageCompression(e2, t3) {
   });
 }
 imageCompression.getDataUrlFromFile = getDataUrlFromFile, imageCompression.getFilefromDataUrl = getFilefromDataUrl, imageCompression.loadImage = loadImage, imageCompression.drawImageInCanvas = drawImageInCanvas, imageCompression.drawFileInCanvas = drawFileInCanvas, imageCompression.canvasToFile = canvasToFile, imageCompression.getExifOrientation = getExifOrientation, imageCompression.handleMaxWidthOrHeight = handleMaxWidthOrHeight, imageCompression.followExifOrientation = followExifOrientation, imageCompression.cleanupCanvasMemory = cleanupCanvasMemory, imageCompression.isAutoOrientationInBrowser = isAutoOrientationInBrowser, imageCompression.approximateBelowMaximumCanvasSizeOfBrowser = approximateBelowMaximumCanvasSizeOfBrowser, imageCompression.copyExifWithoutOrientation = copyExifWithoutOrientation, imageCompression.getBrowserName = getBrowserName, imageCompression.version = "2.0.2";
+// ../../../packages/utility/src/path.ts
+var import_is_subdir = __toESM(require_is_subdir(), 1);
+init_path();
 // ../../../packages/utility/src/string.ts
 var import_lodash2 = __toESM(require_lodash(), 1);
 // ../../../packages/utility/src/tailwind.ts
@@ -162360,7 +162471,7 @@ function anPlusB(token, getNextToken) {
 var PLUSSIGN3 = 43;
 var HYPHENMINUS4 = 45;
 var QUESTIONMARK = 63;
-var U = 117;
+var U2 = 117;
 function isDelim2(token, code5) {
   return token !== null && token.type === Delim && token.value.charCodeAt(0) === code5;
 }
@@ -162398,7 +162509,7 @@ function withQuestionMarkSequence(consumed, length, getNextToken) {
 }
 function urange(token, getNextToken) {
   let length = 0;
-  if (token === null || token.type !== Ident || !cmpChar(token.value, 0, U)) {
+  if (token === null || token.type !== Ident || !cmpChar(token.value, 0, U2)) {
     return 0;
   }
   token = getNextToken(++length);
@@ -164348,7 +164459,7 @@ function createNodeStructureChecker(type, fields) {
     }
   };
 }
-function genTypesList(fieldTypes, path) {
+function genTypesList(fieldTypes, path2) {
   const docsTypes = [];
   for (let i2 = 0;i2 < fieldTypes.length; i2++) {
     const fieldType = fieldTypes[i2];
@@ -164359,9 +164470,9 @@ function genTypesList(fieldTypes, path) {
     } else if (typeof fieldType === "string") {
       docsTypes.push(fieldType);
     } else if (Array.isArray(fieldType)) {
-      docsTypes.push("List<" + (genTypesList(fieldType, path) || "any") + ">");
+      docsTypes.push("List<" + (genTypesList(fieldType, path2) || "any") + ">");
     } else {
-      throw new Error("Wrong value `" + fieldType + "` in `" + path + "` structure definition");
+      throw new Error("Wrong value `" + fieldType + "` in `" + path2 + "` structure definition");
     }
   }
   return docsTypes.join(" | ");
@@ -168884,7 +168995,7 @@ var ASTERISK6 = 42;
 var PLUSSIGN9 = 43;
 var HYPHENMINUS7 = 45;
 var SOLIDUS7 = 47;
-var U2 = 117;
+var U3 = 117;
 function defaultRecognizer(context2) {
   switch (this.tokenType) {
     case Hash:
@@ -168908,7 +169019,7 @@ function defaultRecognizer(context2) {
     case Url:
       return this.Url();
     case Ident:
-      if (this.cmpChar(this.tokenStart, U2) && this.cmpChar(this.tokenStart + 1, PLUSSIGN9)) {
+      if (this.cmpChar(this.tokenStart, U3) && this.cmpChar(this.tokenStart + 1, PLUSSIGN9)) {
         return this.UnicodeRange();
       } else {
         return this.Identifier();
diff --git a/bun.lock b/bun.lock
index ee2d962a4e..acff30aa94 100644
--- a/bun.lock
+++ b/bun.lock
@@ -435,6 +435,7 @@
       "dependencies": {
         "@onlook/fonts": "*",
         "browser-image-compression": "^2.0.2",
+        "is-subdir": "^1.2.0",
         "normalize-url": "^8.0.1",
         "tldts": "^6.1.82",
         "uuid": "^11.1.0",
@@ -1647,6 +1648,8 @@
 
     "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
 
+    "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="],
+
     "bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="],
 
     "bin-links": ["bin-links@5.0.0", "", { "dependencies": { "cmd-shim": "^7.0.0", "npm-normalize-package-bin": "^4.0.0", "proc-log": "^5.0.0", "read-cmd-shim": "^5.0.0", "write-file-atomic": "^6.0.0" } }, "sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA=="],
@@ -2319,6 +2322,8 @@
 
     "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
 
+    "is-subdir": ["is-subdir@1.2.0", "", { "dependencies": { "better-path-resolve": "1.0.0" } }, "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw=="],
+
     "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
 
     "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
@@ -2333,6 +2338,8 @@
 
     "is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
 
+    "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="],
+
     "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
 
     "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
diff --git a/packages/utility/package.json b/packages/utility/package.json
index 8e7faf1c02..6cb46805f7 100644
--- a/packages/utility/package.json
+++ b/packages/utility/package.json
@@ -33,6 +33,7 @@
     "dependencies": {
         "@onlook/fonts": "*",
         "browser-image-compression": "^2.0.2",
+        "is-subdir": "^1.2.0",
         "normalize-url": "^8.0.1",
         "tldts": "^6.1.82",
         "uuid": "^11.1.0"
diff --git a/packages/utility/src/index.ts b/packages/utility/src/index.ts
index 2d956d6872..98c73298a4 100644
--- a/packages/utility/src/index.ts
+++ b/packages/utility/src/index.ts
@@ -10,6 +10,7 @@ export * from './id';
 export * from './image';
 export * from './math';
 export * from './null';
+export * from './path';
 export * from './string';
 export * from './tailwind';
 export * from './time';
diff --git a/packages/utility/src/path.ts b/packages/utility/src/path.ts
new file mode 100644
index 0000000000..c169b4356e
--- /dev/null
+++ b/packages/utility/src/path.ts
@@ -0,0 +1,57 @@
+import isSubdir from 'is-subdir';
+import path from 'path';
+
+// Utility to normalize paths for comparison (handles Windows and POSIX)
+function normalize(p: string): string {
+    if (typeof p !== 'string' || !p) return '';
+    let np = p.replace(/\\/g, '/');
+    // Lowercase drive letter for Windows
+    if (typeof np === 'string' && np.length > 0 && /^[A-Za-z]:\//.test(np)) {
+        np = np[0]?.toLowerCase() + np.slice(1);
+    }
+    return np;
+}
+
+// See: https://www.npmjs.com/package/is-subdir
+// isSubdir(parentDir, subdir) returns true if subdir is the same as or inside parentDir
+export function isSubdirectory(filePath: string, directories: string[]): boolean {
+    const absFilePath = path.resolve(filePath);
+    const normFilePath = normalize(absFilePath);
+    for (const directory of directories) {
+        const absDirectory = path.resolve(directory);
+        const normDirectory = normalize(absDirectory);
+        // Standard is-subdir check
+        if (isSubdir(normDirectory, normFilePath)) {
+            return true;
+        }
+        // If directory is a simple name (like 'foo' or '.git'), check if filePath contains it as a segment
+        if (
+            !directory.includes(path.sep) &&
+            !directory.includes('/') &&
+            !directory.includes('\\')
+        ) {
+            const segments = normFilePath.split('/');
+            if (segments.includes(directory)) {
+                return true;
+            }
+        }
+        // Enhanced: handle mixed absolute/relative by checking if directory segments appear in file path
+        const dirSegments = normalize(directory).split('/').filter(Boolean);
+        const fileSegments = normFilePath.split('/').filter(Boolean);
+        if (dirSegments.length > 0 && fileSegments.length >= dirSegments.length) {
+            for (let i = 0; i <= fileSegments.length - dirSegments.length; i++) {
+                let match = true;
+                for (let j = 0; j < dirSegments.length; j++) {
+                    if (fileSegments[i + j] !== dirSegments[j]) {
+                        match = false;
+                        break;
+                    }
+                }
+                if (match) {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
diff --git a/packages/utility/test/path.test.ts b/packages/utility/test/path.test.ts
new file mode 100644
index 0000000000..64dec04b0e
--- /dev/null
+++ b/packages/utility/test/path.test.ts
@@ -0,0 +1,146 @@
+import { describe, expect, test } from 'bun:test';
+import { isSubdirectory } from '../src/path';
+
+describe('isSubdirectory', () => {
+    test('returns true for direct subdirectory', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['/project/sandbox/foo'])).toBe(true);
+    });
+
+    test('returns true for nested subdirectory', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar/baz.txt', ['/project/sandbox/foo'])).toBe(
+            true,
+        );
+    });
+
+    test('returns false for file outside directory', () => {
+        expect(isSubdirectory('/project/sandbox2/foo/bar.txt', ['/project/sandbox/foo'])).toBe(
+            false,
+        );
+    });
+
+    test('returns true for file in the directory itself', () => {
+        expect(isSubdirectory('/project/sandbox/foo', ['/project/sandbox/foo'])).toBe(true);
+    });
+
+    test('returns false for file in sibling directory', () => {
+        expect(isSubdirectory('/project/sandbox/bar/baz.txt', ['/project/sandbox/foo'])).toBe(
+            false,
+        );
+    });
+
+    test('returns true for multiple directories (one matches)', () => {
+        expect(
+            isSubdirectory('/project/sandbox/foo/bar.txt', [
+                '/project/sandbox/other',
+                '/project/sandbox/foo',
+            ]),
+        ).toBe(true);
+    });
+
+    test('returns false for multiple directories (none match)', () => {
+        expect(
+            isSubdirectory('/project/sandbox/foo/bar.txt', [
+                '/project/sandbox/other',
+                '/project/sandbox/else',
+            ]),
+        ).toBe(false);
+    });
+
+    test('handles relative file paths', () => {
+        expect(isSubdirectory('foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('foo/bar/baz.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('bar/baz.txt', ['foo'])).toBe(false);
+    });
+
+    test('handles relative directory paths', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['foo/bar'])).toBe(false);
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['bar'])).toBe(false);
+    });
+
+    test('returns false for empty directories array', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', [])).toBe(false);
+    });
+
+    test('returns true for root directory', () => {
+        expect(isSubdirectory('/project/sandbox/foo.txt', ['/project/sandbox'])).toBe(true);
+    });
+
+    test('handles Windows-style paths', () => {
+        expect(isSubdirectory('C:/project/sandbox/foo/bar.txt', ['C:/project/sandbox/foo'])).toBe(
+            true,
+        );
+        expect(isSubdirectory('C:/project/sandbox/foo/bar.txt', ['C:/project/sandbox/other'])).toBe(
+            false,
+        );
+        expect(
+            isSubdirectory('C:\\project\\sandbox\\foo\\bar.txt', ['C:\\project\\sandbox\\foo']),
+        ).toBe(true);
+    });
+
+    test('returns true if filePath is exactly the directory', () => {
+        expect(isSubdirectory('/project/sandbox/foo', ['/project/sandbox/foo'])).toBe(true);
+    });
+
+    test('returns false if filePath is parent of directory', () => {
+        expect(isSubdirectory('/project/sandbox', ['/project/sandbox/foo'])).toBe(false);
+    });
+
+    test('handles .git directory with parent path', () => {
+        expect(
+            isSubdirectory('../home/csb-session-000000000000013wf4ua/workspace/.git/FETCH_HEAD', [
+                '.git',
+            ]),
+        ).toBe(true);
+    });
+
+    test('absolute file and directory paths (POSIX)', () => {
+        expect(isSubdirectory('/a/b/c/file.txt', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/c/d/file.txt', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/c', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/c', ['/a/b/c/'])).toBe(true);
+        expect(isSubdirectory('/a/b/c/', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/c/../c/file.txt', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/d/file.txt', ['/a/b/c'])).toBe(false);
+    });
+
+    test('relative file and directory paths', () => {
+        expect(isSubdirectory('foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('foo/bar/baz.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('foo', ['foo'])).toBe(true);
+        expect(isSubdirectory('foo/', ['foo'])).toBe(true);
+        expect(isSubdirectory('foo/../foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('bar/baz.txt', ['foo'])).toBe(false);
+    });
+
+    test('absolute file, relative directory', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['bar'])).toBe(false);
+    });
+
+    test('relative file, absolute directory', () => {
+        expect(isSubdirectory('sandbox/foo/bar.txt', ['/sandbox/foo'])).toBe(true);
+        expect(isSubdirectory('sandbox/bar.txt', ['/sandbox/foo'])).toBe(false);
+    });
+
+    test('mixed absolute and relative paths', () => {
+        expect(isSubdirectory('/a/b/c/file.txt', ['b/c'])).toBe(true);
+        expect(isSubdirectory('a/b/c/file.txt', ['/a/b'])).toBe(true);
+        expect(isSubdirectory('/a/b/c/file.txt', ['a/b'])).toBe(true);
+        expect(isSubdirectory('a/b/c/file.txt', ['/a/b/c'])).toBe(true);
+    });
+
+    test('edge cases: trailing slashes, dot segments, case sensitivity', () => {
+        expect(isSubdirectory('/A/B/C/file.txt', ['/A/B/C'])).toBe(true);
+        expect(isSubdirectory('/A/B/C/file.txt', ['/a/b/c'])).toBe(false); // case sensitive
+        expect(isSubdirectory('/a/b/c/./file.txt', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/c/../c/file.txt', ['/a/b/c'])).toBe(true);
+        expect(isSubdirectory('/a/b/c', ['/a/b/c/.'])).toBe(true);
+    });
+
+    test('negative cases: file outside, above, or in sibling directories', () => {
+        expect(isSubdirectory('/a/b/file.txt', ['/a/b/c'])).toBe(false);
+        expect(isSubdirectory('/a/b/c/../../file.txt', ['/a/b/c'])).toBe(false);
+        expect(isSubdirectory('/a/b/d/file.txt', ['/a/b/c'])).toBe(false);
+    });
+});


Reviews:
user: login: ellipsis-dev[bot]
body: > [!CAUTION]
> **Changes requested  :x:**
> <details>
> <summary>Reviewed everything up to 9c750292b407c61a67eefb14e404d8074b39e584 in <ellipsistime>2 minutes and 3 seconds</ellipsistime>. Click for details.</summary> 
> 
> - Reviewed `132` lines of code in `3` files
> - Skipped `0` files when reviewing.
> - Skipped posting `1` draft comments. View those below.
> - Modify your [settings](https://app.ellipsis.dev/onlook-dev/settings/code-review) and [rules](https://app.ellipsis.dev/onlook-dev/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>apps/web/client/src/app/project/[id]/_components/editor-bar/index.tsx:12</code></summary>
> 
> - **Draft comment:** 
> Expanded text tag list: confirm inclusion of interactive elements (e.g. <a>) is intentional.
> - **Reason this comment was not posted:**
> Confidence changes required: `33%` <= threshold `50%` None
> 
> 
> </details>
> 
> 
> Workflow ID: <workflowid>`wflow_OgnM82NmhwG0Wl2f`</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=onlook-dev%2Fonlook&utm_source=github&utm_medium=referral)<sup> by changing your [verbosity settings](https://app.ellipsis.dev/onlook-dev/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/onlook-dev/code-review/rules).</sup>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T19:58:24Z
user: login: ellipsis-dev[bot]
body: > [!IMPORTANT]
> **Looks good to me!  :+1:**
> <details>
> <summary>Reviewed 040131f856bcecd6c0365e1a89c2ba5f825b71f6 in <ellipsistime>1 minute and 14 seconds</ellipsistime>. Click for details.</summary> 
> 
> - Reviewed `80` lines of code in `1` files
> - Skipped `0` files when reviewing.
> - Skipped posting `2` draft comments. View those below.
> - Modify your [settings](https://app.ellipsis.dev/onlook-dev/settings/code-review) and [rules](https://app.ellipsis.dev/onlook-dev/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>apps/web/client/test/sandbox/helpers.test.ts:3</code></summary>
> 
> - **Draft comment:** 
> Removed 'isSubdirectory' from the import. Ensure that its removal is intentional and that the production code no longer needs it.
> - **Reason this comment was not posted:**
> Comment looked like it was already resolved.
> 
> 
> </details>
> 
> <details>
> <summary>2. <code>apps/web/client/test/sandbox/helpers.test.ts:44</code></summary>
> 
> - **Draft comment:** 
> The entire isSubdirectory test suite has been removed. Confirm that this function is no longer in use (or that its tests exist elsewhere) to maintain proper coverage.
> - **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_4wX1DuoTnXxe5b3m`</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=onlook-dev%2Fonlook&utm_source=github&utm_medium=referral)<sup> by changing your [verbosity settings](https://app.ellipsis.dev/onlook-dev/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/onlook-dev/code-review/rules).</sup>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:00:18Z
user: login: ellipsis-dev[bot]
body: > [!CAUTION]
> **Changes requested  :x:**
> <details>
> <summary>Reviewed 21b10ff74b69435aac87bd6e1fad3e8a211950cd in <ellipsistime>2 minutes and 9 seconds</ellipsistime>. Click for details.</summary> 
> 
> - Reviewed `114` lines of code in `2` files
> - Skipped `0` files when reviewing.
> - Skipped posting `3` draft comments. View those below.
> - Modify your [settings](https://app.ellipsis.dev/onlook-dev/settings/code-review) and [rules](https://app.ellipsis.dev/onlook-dev/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>apps/web/client/test/sandbox/helpers.test.ts:35</code></summary>
> 
> - **Draft comment:** 
> When mocking path methods, use try/finally (or equivalent) to ensure originals are restored even if assertions fail.
> - **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>apps/web/client/test/sandbox/helpers.test.ts:77</code></summary>
> 
> - **Draft comment:** 
> Clarify behavior for relative directory paths; ensure that 'foo/bar' is not matched when file is 'foo/bar.txt' is intentional per design.
> - **Reason this comment was not posted:**
> Confidence changes required: `33%` <= threshold `50%` None
> 
> 
> </details>
> 
> <details>
> <summary>3. <code>apps/web/client/test/sandbox/subdirectory.test.ts:1</code></summary>
> 
> - **Draft comment:** 
> This file duplicates isSubdirectory tests already present in helpers.test.ts. Consider consolidating to avoid redundancy.
> - **Reason this comment was not posted:**
> Comment was on unchanged code.
> 
> 
> </details>
> 
> 
> Workflow ID: <workflowid>`wflow_u3hdLGBgktD5YyOf`</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=onlook-dev%2Fonlook&utm_source=github&utm_medium=referral)<sup> by changing your [verbosity settings](https://app.ellipsis.dev/onlook-dev/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/onlook-dev/code-review/rules).</sup>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:00:25Z
user: login: ellipsis-dev[bot]
body: > [!IMPORTANT]
> **Looks good to me!  :+1:**
> <details>
> <summary>Reviewed b1db74523ca21dc7f7a4f9e4c709eabd6fe6c45c in <ellipsistime>36 seconds</ellipsistime>. Click for details.</summary> 
> 
> - Reviewed `16` lines of code in `1` files
> - Skipped `0` files when reviewing.
> - Skipped posting `1` draft comments. View those below.
> - Modify your [settings](https://app.ellipsis.dev/onlook-dev/settings/code-review) and [rules](https://app.ellipsis.dev/onlook-dev/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>apps/web/client/src/components/store/editor/sandbox/helpers.ts:23</code></summary>
> 
> - **Draft comment:** 
> Removed redundant no-op if block. This cleanup is safe since it didn't alter absFilePath.
> - **Reason this comment was not posted:**
> Confidence changes required: `0%` <= threshold `50%` None
> 
> 
> </details>
> 
> 
> Workflow ID: <workflowid>`wflow_lOMXx9dlR5uHnZvO`</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=onlook-dev%2Fonlook&utm_source=github&utm_medium=referral)<sup> by changing your [verbosity settings](https://app.ellipsis.dev/onlook-dev/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/onlook-dev/code-review/rules).</sup>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:01:21Z
user: login: ellipsis-dev[bot]
body: > [!CAUTION]
> **Changes requested  :x:**
> <details>
> <summary>Reviewed 9b6292d1dcbe42536b616bd5f2bbde58cde39e1a in <ellipsistime>1 minute and 45 seconds</ellipsistime>. Click for details.</summary> 
> 
> - Reviewed `121` lines of code in `2` files
> - Skipped `0` files when reviewing.
> - Skipped posting `3` draft comments. View those below.
> - Modify your [settings](https://app.ellipsis.dev/onlook-dev/settings/code-review) and [rules](https://app.ellipsis.dev/onlook-dev/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>packages/utility/src/path.ts:29</code></summary>
> 
> - **Draft comment:** 
> Consider simplifying the check for simple directory names. Since paths are normalized to use '/', checking both '/' and '\\' (and path.sep) may be redundant.
> - **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>packages/utility/src/path.ts:39</code></summary>
> 
> - **Draft comment:** 
> Cache the normalized directory value before splitting into segments to avoid redundant normalization.
> - **Reason this comment was not posted:**
> Confidence changes required: `33%` <= threshold `50%` None
> 
> 
> </details>
> 
> <details>
> <summary>3. <code>packages/utility/test/path.test.ts:145</code></summary>
> 
> - **Draft comment:** 
> Test coverage is extensive. As an enhancement, consider adding test cases for empty filePath or directory inputs to further ensure robustness.
> - **Reason this comment was not posted:**
> Confidence changes required: `33%` <= threshold `50%` None
> 
> 
> </details>
> 
> 
> Workflow ID: <workflowid>`wflow_zTKhmACSFRwRjboM`</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=onlook-dev%2Fonlook&utm_source=github&utm_medium=referral)<sup> by changing your [verbosity settings](https://app.ellipsis.dev/onlook-dev/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/onlook-dev/code-review/rules).</sup>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T21:40:12Z

Review comments:
diff_hunk: @@ -12,14 +12,32 @@ export function normalizePath(p: string): string {
 }
 
 export function isSubdirectory(filePath: string, directories: string[]): boolean {
-    // Normalize the file path by replacing backslashes with forward slashes
-    const normalizedFilePath = path.resolve(filePath.replace(/\\/g, '/'));
+    // Always normalize to forward slashes and remove trailing slashes
+    function normalize(p: string): string {
+        let s = p.replace(/\\/g, '/');
+        // Remove trailing slashes (but not for root)
+        if (s.length > 1 && s.endsWith('/')) s = s.replace(/\/+$/, '');
+        return s;
+    }
+    // Always resolve filePath relative to sandbox root if not absolute, but also if it starts with SANDBOX_ROOT, keep as is
+    let absFilePath = path.isAbsolute(filePath) ? filePath : path.join(SANDBOX_ROOT, filePath);
+    // If filePath is absolute but inside SANDBOX_ROOT, keep as is
+    if (absFilePath.startsWith(SANDBOX_ROOT + '/')) {
path: apps/web/client/src/components/store/editor/sandbox/helpers.ts
user: login: ellipsis-dev[bot]
body: Redundant if statement: `if (absFilePath.startsWith(SANDBOX_ROOT + '/')) { absFilePath = absFilePath; }` does nothing and can be removed.

created_at: 2025-05-21T19:58:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+import { describe, expect, test } from 'bun:test';
+import { isSubdirectory } from '../../src/components/store/editor/sandbox/helpers';
+
+// Helper to simulate sandbox root
+const SANDBOX_ROOT = '/project/sandbox';
path: apps/web/client/test/sandbox/subdirectory.test.ts
user: login: ellipsis-dev[bot]
body: Unused constant `SANDBOX_ROOT` in tests; consider removing it to avoid confusion.
```suggestion

```

created_at: 2025-05-21T19:58:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,61 +46,65 @@ describe('normalizePath', () => {
 });
 
 describe('isSubdirectory', () => {
-    test('should return true if filePath is a subdirectory of directory', () => {
-        expect(isSubdirectory('dir/file.txt', ['dir'])).toBe(true);
+    test('returns true for direct subdirectory', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['/project/sandbox/foo'])).toBe(true);
     });
 
-    test('should return false if filePath is not a subdirectory of directory', () => {
-        expect(isSubdirectory('dir/file.txt', ['other'])).toBe(false);
+    test('returns true for nested subdirectory', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar/baz.txt', ['/project/sandbox/foo'])).toBe(true);
     });
 
-    test('should return true if filePath is a subdirectory of one of multiple directories', () => {
-        expect(isSubdirectory('dir/file.txt', ['dir', 'other'])).toBe(true);
+    test('returns false for file outside directory', () => {
+        expect(isSubdirectory('/project/sandbox2/foo/bar.txt', ['/project/sandbox/foo'])).toBe(false);
     });
 
-    test('should return true if filePath is under a nested directory in list', () => {
-        expect(isSubdirectory('hi/dir/file.txt', ['dir', 'hi'])).toBe(true);
+    test('returns true for file in the directory itself', () => {
+        expect(isSubdirectory('/project/sandbox/foo', ['/project/sandbox/foo'])).toBe(true);
     });
 
-    test('should return true if filePath matches directory exactly', () => {
-        expect(isSubdirectory('dir', ['dir'])).toBe(true);
+    test('returns false for file in sibling directory', () => {
+        expect(isSubdirectory('/project/sandbox/bar/baz.txt', ['/project/sandbox/foo'])).toBe(false);
     });
 
-    test('should return true if filePath matches directory with trailing slash', () => {
-        expect(isSubdirectory('dir/', ['dir'])).toBe(true);
+    test('returns true for multiple directories (one matches)', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['/project/sandbox/other', '/project/sandbox/foo'])).toBe(true);
     });
 
-    test('should return false if filePath is only a prefix match', () => {
-        expect(isSubdirectory('dir2/file.txt', ['dir'])).toBe(false);
+    test('returns false for multiple directories (none match)', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['/project/sandbox/other', '/project/sandbox/else'])).toBe(false);
     });
 
-    test('should return true if absolute paths are used', () => {
-        expect(isSubdirectory('/home/user/dir/file.txt', ['/home/user/dir'])).toBe(true);
+    test('handles relative file paths', () => {
+        expect(isSubdirectory('foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('foo/bar/baz.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('bar/baz.txt', ['foo'])).toBe(false);
     });
 
-    test('should return false if absolute paths mismatch', () => {
-        expect(isSubdirectory('/home/user/other/file.txt', ['/home/user/dir'])).toBe(false);
+    test('handles relative directory paths', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['foo'])).toBe(true);
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['foo/bar'])).toBe(false);
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', ['bar'])).toBe(false);
     });
 
-    test('should handle mixed separators', () => {
-        expect(isSubdirectory('dir\\file.txt', ['dir'])).toBe(true);
+    test('returns false for empty directories array', () => {
+        expect(isSubdirectory('/project/sandbox/foo/bar.txt', [])).toBe(false);
     });
 
-    test('should return false for empty directories list', () => {
-        expect(isSubdirectory('dir/file.txt', [])).toBe(false);
+    test('returns true for root directory', () => {
+        expect(isSubdirectory('/project/sandbox/foo.txt', ['/project/sandbox'])).toBe(true);
     });
 
-    test('should handle directory paths with trailing slashes', () => {
-        expect(isSubdirectory('dir/file.txt', ['dir/'])).toBe(true);
+    test('handles Windows-style paths', () => {
+        expect(isSubdirectory('C:/project/sandbox/foo/bar.txt', ['C:/project/sandbox/foo'])).toBe(true);
+        expect(isSubdirectory('C:/project/sandbox/foo/bar.txt', ['C:/project/sandbox/other'])).toBe(false);
+        expect(isSubdirectory('C:\\project\\sandbox\\foo\\bar.txt', ['C:\\project\\sandbox\\foo'])).toBe(true);
     });
 
-    test('should handle complex nested paths', () => {
-        expect(isSubdirectory('dir/subdir/file.txt', ['dir'])).toBe(true);
-        expect(isSubdirectory('dir/subdir/file.txt', ['dir/subdir'])).toBe(true);
-        expect(isSubdirectory('dir/subdir/file.txt', ['dir/subdir/'])).toBe(true);
+    test('returns true if filePath is exactly the directory', () => {
path: apps/web/client/test/sandbox/helpers.test.ts
user: login: ellipsis-dev[bot]
body: Duplicate test for exact directory match; already tested at line 61. Consider removing one.

created_at: 2025-05-21T20:00:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+import isSubdir from 'is-subdir';
+import path from 'path';
+
+// Utility to normalize paths for comparison (handles Windows and POSIX)
+function normalize(p: string): string {
+    if (typeof p !== 'string' || !p) return '';
+    let np = p.replace(/\\/g, '/');
+    // Lowercase drive letter for Windows
+    if (typeof np === 'string' && np.length > 0 && /^[A-Za-z]:\//.test(np)) {
+        np = np[0]?.toLowerCase() + np.slice(1);
path: packages/utility/src/path.ts
user: login: ellipsis-dev[bot]
body: Optional chaining (`np[0]?.toLowerCase()`) is redundant since `np.length > 0` is already checked.
```suggestion
        np = np[0].toLowerCase() + np.slice(1);
```

created_at: 2025-05-21T21:40:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-21T19:56:21Z
body: [vc]: #gVfxVI1jJw33hUM/K72ruMen4vcGBifOQ37WzmkMs2o=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJkb2NzIiwicm9vdERpcmVjdG9yeSI6ImRvY3MiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vb25sb29rL2RvY3MvR1lReEJ5N0F4cFpMbnUxcVFxSjlRZVl2ZXNUeCIsInByZXZpZXdVcmwiOiJkb2NzLWdpdC1idWdzLW1vcmUtdGV4dC10YWdzLW9ubG9vay52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImRvY3MtZ2l0LWJ1Z3MtbW9yZS10ZXh0LXRhZ3Mtb25sb29rLnZlcmNlbC5hcHAifX0seyJuYW1lIjoid2ViIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvd2ViL2NsaWVudCIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9vbmxvb2svd2ViL0VGalhvOU5zWlRkcmNURVpESkpwVGRzYXNXdHAiLCJwcmV2aWV3VXJsIjoid2ViLWdpdC1idWdzLW1vcmUtdGV4dC10YWdzLW9ubG9vay52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IndlYi1naXQtYnVncy1tb3JlLXRleHQtdGFncy1vbmxvb2sudmVyY2VsLmFwcCJ9fV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **docs** | ✅ Ready ([Inspect](https://vercel.com/onlook/docs/GYQxBy7AxpZLnu1qQqJ9QeYvesTx)) | [Visit Preview](https://docs-git-bugs-more-text-tags-onlook.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/docs-git-bugs-more-text-tags-onlook.vercel.app?via=pr-comment-feedback-link) | May 21, 2025 9:43pm |
| **web** | ✅ Ready ([Inspect](https://vercel.com/onlook/web/EFjXo9NsZTdrcTEZDJJpTdsasWtp)) | [Visit Preview](https://web-git-bugs-more-text-tags-onlook.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/web-git-bugs-more-text-tags-onlook.vercel.app?via=pr-comment-feedback-link) | May 21, 2025 9:43pm |



author_association: NONE
user: login: supabase[bot]
created_at: 2025-05-21T19:56:22Z
body: [supa]:wowaemfasoptxrdjhilu
This pull request has been ignored for the connected project `wowaemfasoptxrdjhilu` because there are no changes detected in `apps/backend/supabase` directory. You can change this behaviour in [Project Integrations Settings ↗︎](https://supabase.com/dashboard/project/wowaemfasoptxrdjhilu/settings/integrations).

<hr/>

Preview Branches by Supabase.
Learn more about [Supabase Branching ↗︎](https://supabase.com/docs/guides/platform/branching).
author_association: NONE