@mongoosejs/studio 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,10 @@
9
9
 
10
10
  var map = {
11
11
  "./": "./frontend/src/index.js",
12
+ "./_util/baseComponent": "./frontend/src/_util/baseComponent.js",
13
+ "./_util/baseComponent.js": "./frontend/src/_util/baseComponent.js",
14
+ "./_util/deepEqual": "./frontend/src/_util/deepEqual.js",
15
+ "./_util/deepEqual.js": "./frontend/src/_util/deepEqual.js",
12
16
  "./_util/document-search-autocomplete": "./frontend/src/_util/document-search-autocomplete.js",
13
17
  "./_util/document-search-autocomplete.js": "./frontend/src/_util/document-search-autocomplete.js",
14
18
  "./api": "./frontend/src/api.js",
@@ -208,6 +212,100 @@ webpackContext.resolve = webpackContextResolve;
208
212
  module.exports = webpackContext;
209
213
  webpackContext.id = "./frontend/src sync recursive ^\\.\\/.*$";
210
214
 
215
+ /***/ },
216
+
217
+ /***/ "./frontend/src/_util/baseComponent.js"
218
+ /*!*********************************************!*\
219
+ !*** ./frontend/src/_util/baseComponent.js ***!
220
+ \*********************************************/
221
+ (module) {
222
+
223
+ "use strict";
224
+
225
+
226
+ module.exports = {
227
+ destroyed() {
228
+ this.$parent.$options.$children = this.$parent.$options.$children.filter(
229
+ (el) => el !== this
230
+ );
231
+ },
232
+ created() {
233
+ this.$parent.$options.$children = this.$parent.$options.$children || [];
234
+ this.$parent.$options.$children.push(this);
235
+ },
236
+ mounted() {
237
+ this.isMounted = true;
238
+ },
239
+ unmounted() {
240
+ this.isMounted = false;
241
+ }
242
+ };
243
+
244
+
245
+ /***/ },
246
+
247
+ /***/ "./frontend/src/_util/deepEqual.js"
248
+ /*!*****************************************!*\
249
+ !*** ./frontend/src/_util/deepEqual.js ***!
250
+ \*****************************************/
251
+ (module) {
252
+
253
+ "use strict";
254
+
255
+
256
+ /**
257
+ * Deep equality check for values (handles primitives, arrays, objects, dates, etc.)
258
+ * @param {*} a - First value to compare
259
+ * @param {*} b - Second value to compare
260
+ * @returns {boolean} - True if values are deeply equal
261
+ */
262
+ function deepEqual(a, b) {
263
+ // Handle primitives and same reference
264
+ if (a === b) return true;
265
+
266
+ // Handle null and undefined
267
+ if (a == null || b == null) return a === b;
268
+
269
+ // Handle different types
270
+ if (typeof a !== typeof b) return false;
271
+
272
+ // Handle dates
273
+ if (a instanceof Date && b instanceof Date) {
274
+ return a.getTime() === b.getTime();
275
+ }
276
+
277
+ // Handle arrays - must both be arrays
278
+ if (Array.isArray(a) || Array.isArray(b)) {
279
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
280
+ if (a.length !== b.length) return false;
281
+ for (let i = 0; i < a.length; i++) {
282
+ if (!deepEqual(a[i], b[i])) return false;
283
+ }
284
+ return true;
285
+ }
286
+
287
+ // Handle objects (non-arrays)
288
+ if (typeof a === 'object' && typeof b === 'object') {
289
+ const keysA = Object.keys(a);
290
+ const keysB = Object.keys(b);
291
+
292
+ if (keysA.length !== keysB.length) return false;
293
+
294
+ for (const key of keysA) {
295
+ if (!keysB.includes(key)) return false;
296
+ if (!deepEqual(a[key], b[key])) return false;
297
+ }
298
+
299
+ return true;
300
+ }
301
+
302
+ // Fallback for primitives (strings, numbers, booleans)
303
+ return false;
304
+ }
305
+
306
+ module.exports = deepEqual;
307
+
308
+
211
309
  /***/ },
212
310
 
213
311
  /***/ "./frontend/src/_util/document-search-autocomplete.js"
@@ -301,9 +399,9 @@ function getAutocompleteContext(searchText, cursorPos) {
301
399
 
302
400
  // Check if we're in a value context (after a colon)
303
401
  // Match the last colon followed by optional whitespace and capture everything after
304
- const valueMatch = before.match(/:\s*([^\s,\}\]:]*)$/);
402
+ const valueMatch = before.match(/:\s*(\{?\s*)([^\s,\}\]:]*)$/);
305
403
  if (valueMatch) {
306
- const token = valueMatch[1];
404
+ const token = valueMatch[2];
307
405
  return {
308
406
  token,
309
407
  role: 'value',
@@ -378,9 +476,9 @@ function applySuggestion(searchText, cursorPos, suggestion) {
378
476
  const after = searchText.slice(cursorPos);
379
477
 
380
478
  // Check if we're in a value context
381
- const valueMatch = before.match(/:\s*([^\s,\}\]:]*)$/);
479
+ const valueMatch = before.match(/:\s*(\{?\s*)([^\s,\}\]:]*)$/);
382
480
  if (valueMatch) {
383
- const token = valueMatch[1];
481
+ const token = valueMatch[2];
384
482
  const start = cursorPos - token.length;
385
483
  let replacement = suggestion;
386
484
  let cursorOffset = replacement.length;
@@ -855,6 +953,7 @@ module.exports = function appendCSS(css) {
855
953
 
856
954
 
857
955
  const { inspect } = __webpack_require__(/*! node-inspect-extracted */ "./node_modules/node-inspect-extracted/dist/inspect.js");
956
+ const deepEqual = __webpack_require__(/*! ./_util/deepEqual */ "./frontend/src/_util/deepEqual.js");
858
957
 
859
958
  /**
860
959
  * Format a value for display in array views
@@ -2254,10 +2353,12 @@ module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'load
2254
2353
 
2255
2354
 
2256
2355
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
2356
+ const baseComponent = __webpack_require__(/*! ../_util/baseComponent */ "./frontend/src/_util/baseComponent.js");
2257
2357
  const template = __webpack_require__(/*! ./dashboard.html */ "./frontend/src/dashboard/dashboard.html");
2258
2358
 
2259
- module.exports = app => app.component('dashboard', {
2359
+ module.exports = {
2260
2360
  template: template,
2361
+ extends: baseComponent,
2261
2362
  props: ['dashboardId'],
2262
2363
  data: function() {
2263
2364
  return {
@@ -2301,7 +2402,7 @@ module.exports = app => app.component('dashboard', {
2301
2402
  }
2302
2403
  },
2303
2404
  shouldEvaluateDashboard() {
2304
- if (this.dashboardResults.length === 0) {
2405
+ if (!this.dashboardResults || this.dashboardResults.length === 0) {
2305
2406
  return true;
2306
2407
  }
2307
2408
 
@@ -2368,6 +2469,22 @@ module.exports = app => app.component('dashboard', {
2368
2469
  this.startingChat = false;
2369
2470
  this.showActionsMenu = false;
2370
2471
  }
2472
+ },
2473
+ async loadInitial() {
2474
+ const { dashboard, dashboardResults, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: false });
2475
+ if (!dashboard) {
2476
+ return;
2477
+ }
2478
+ this.dashboard = dashboard;
2479
+ this.code = this.dashboard.code;
2480
+ this.title = this.dashboard.title;
2481
+ this.description = this.dashboard.description ?? '';
2482
+ this.dashboardResults = dashboardResults;
2483
+ if (this.shouldEvaluateDashboard()) {
2484
+ await this.evaluateDashboard();
2485
+ return;
2486
+ }
2487
+ this.status = 'loaded';
2371
2488
  }
2372
2489
  },
2373
2490
  computed: {
@@ -2378,25 +2495,12 @@ module.exports = app => app.component('dashboard', {
2378
2495
  mounted: async function() {
2379
2496
  document.addEventListener('click', this.handleDocumentClick);
2380
2497
  this.showEditor = this.$route.query.edit;
2381
- const { dashboard, dashboardResults, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: false });
2382
- if (!dashboard) {
2383
- return;
2384
- }
2385
- this.dashboard = dashboard;
2386
- this.code = this.dashboard.code;
2387
- this.title = this.dashboard.title;
2388
- this.description = this.dashboard.description ?? '';
2389
- this.dashboardResults = dashboardResults;
2390
- if (this.shouldEvaluateDashboard()) {
2391
- await this.evaluateDashboard();
2392
- return;
2393
- }
2394
- this.status = 'loaded';
2498
+ await this.loadInitial();
2395
2499
  },
2396
2500
  beforeDestroy() {
2397
2501
  document.removeEventListener('click', this.handleDocumentClick);
2398
2502
  }
2399
- });
2503
+ };
2400
2504
 
2401
2505
 
2402
2506
  /***/ },
@@ -3181,7 +3285,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
3181
3285
  (module) {
3182
3286
 
3183
3287
  "use strict";
3184
- module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-gray-100': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <!-- Special handling for truncated arrays -->\n <div v-if=\"isArray && shouldShowTruncated\" class=\"w-full\">\n <div class=\"mt-2\">\n <div\n v-for=\"(item, index) in truncatedArrayItems\"\n :key=\"index\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600\">\n <div class=\"absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600\">{{ index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(item) }}</div>\n </div>\n <div class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-gray-500 italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n </div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show all {{ arrayValue.length }} items\n </button>\n </div>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
3288
+ module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-gray-100': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <!-- Special handling for truncated arrays -->\n <div v-if=\"isArray && shouldShowTruncated\" class=\"w-full\">\n <div class=\"mt-2\">\n <div\n v-for=\"(item, index) in truncatedArrayItems\"\n :key=\"index\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600\">\n <div class=\"absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600\">{{ index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(item) }}</div>\n </div>\n <div class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-gray-500 italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n </div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show all {{ arrayValue.length }} items\n </button>\n </div>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
3185
3289
 
3186
3290
  /***/ },
3187
3291
 
@@ -3197,7 +3301,7 @@ module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\"
3197
3301
 
3198
3302
 
3199
3303
  const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
3200
- const { inspect } = __webpack_require__(/*! node-inspect-extracted */ "./node_modules/node-inspect-extracted/dist/inspect.js");
3304
+ const deepEqual = __webpack_require__(/*! ../../_util/deepEqual */ "./frontend/src/_util/deepEqual.js");
3201
3305
  const template = __webpack_require__(/*! ./document-property.html */ "./frontend/src/document-details/document-property/document-property.html");
3202
3306
 
3203
3307
  const appendCSS = __webpack_require__(/*! ../../appendCSS */ "./frontend/src/appendCSS.js");
@@ -3289,6 +3393,20 @@ module.exports = app => app.component('document-property', {
3289
3393
  }
3290
3394
  },
3291
3395
  methods: {
3396
+ handleInputChange(newValue) {
3397
+ const currentValue = this.getValueForPath(this.path.path);
3398
+
3399
+ // Only record as a change if the value is actually different
3400
+ if (!deepEqual(currentValue, newValue)) {
3401
+ this.changes[this.path.path] = newValue;
3402
+ } else {
3403
+ // If the value is the same as the original, remove it from changes
3404
+ delete this.changes[this.path.path];
3405
+ }
3406
+
3407
+ // Always clear invalid state on input
3408
+ delete this.invalid[this.path.path];
3409
+ },
3292
3410
  getComponentForPath(schemaPath) {
3293
3411
  if (schemaPath.instance === 'Array') {
3294
3412
  return 'detail-array';
@@ -3434,6 +3552,16 @@ module.exports = app => app.component('confirm-changes', {
3434
3552
  props: ['value'],
3435
3553
  computed: {
3436
3554
  displayValue() {
3555
+ const hasUnsetFields = Object.keys(this.value).some(key => this.value[key] === undefined);
3556
+ if (hasUnsetFields) {
3557
+ const unsetFields = Object.keys(this.value)
3558
+ .filter(key => this.value[key] === undefined)
3559
+ .reduce((obj, key) => Object.assign(obj, { [key]: 1 }), {});
3560
+ const setFields = Object.keys(this.value)
3561
+ .filter(key => this.value[key] !== undefined)
3562
+ .reduce((obj, key) => Object.assign(obj, { [key]: this.value[key] }), {});
3563
+ return JSON.stringify({ $set: setFields, $unset: unsetFields }, null, ' ').trim();
3564
+ }
3437
3565
  return JSON.stringify(this.value, null, ' ').trim();
3438
3566
  }
3439
3567
  },
@@ -3450,6 +3578,7 @@ module.exports = app => app.component('confirm-changes', {
3450
3578
  }
3451
3579
  });
3452
3580
 
3581
+
3453
3582
  /***/ },
3454
3583
 
3455
3584
  /***/ "./frontend/src/document/confirm-delete/confirm-delete.html"
@@ -3515,7 +3644,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
3515
3644
  (module) {
3516
3645
 
3517
3646
  "use strict";
3518
- module.exports = "<div class=\"document px-1 pt-4 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n </div>\n</div>\n";
3647
+ module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n </div>\n</div>\n";
3519
3648
 
3520
3649
  /***/ },
3521
3650
 
@@ -3602,10 +3731,25 @@ module.exports = app => app.component('document', {
3602
3731
  if (Object.keys(this.invalid).length > 0) {
3603
3732
  throw new Error('Invalid paths: ' + Object.keys(this.invalid).join(', '));
3604
3733
  }
3734
+
3735
+ let update = this.changes;
3736
+ let unset = {};
3737
+ const hasUnsetFields = Object.keys(this.changes)
3738
+ .some(key => this.changes[key] === undefined);
3739
+ if (hasUnsetFields) {
3740
+ unset = Object.keys(this.changes)
3741
+ .filter(key => this.changes[key] === undefined)
3742
+ .reduce((obj, key) => Object.assign(obj, { [key]: 1 }), {});
3743
+ update = Object.keys(this.changes)
3744
+ .filter(key => this.changes[key] !== undefined)
3745
+ .reduce((obj, key) => Object.assign(obj, { [key]: this.changes[key] }), {});
3746
+ }
3747
+
3605
3748
  const { doc } = await api.Model.updateDocument({
3606
3749
  model: this.model,
3607
3750
  _id: this.document._id,
3608
- update: this.changes
3751
+ update,
3752
+ unset
3609
3753
  });
3610
3754
  this.document = doc;
3611
3755
  this.changes = {};
@@ -3833,20 +3977,12 @@ module.exports = app => app.component('edit-boolean', {
3833
3977
  this.selectedValue = newValue;
3834
3978
  },
3835
3979
  selectedValue(newValue) {
3836
- // Convert null/undefined to strings for proper backend serialization
3837
- const emitValue = this.convertValueToString(newValue);
3838
- this.$emit('input', emitValue);
3980
+ this.$emit('input', newValue);
3839
3981
  }
3840
3982
  },
3841
3983
  methods: {
3842
3984
  selectValue(value) {
3843
3985
  this.selectedValue = value;
3844
- },
3845
- convertValueToString(value) {
3846
- // Convert null/undefined to strings for proper backend serialization
3847
- if (value === null) return 'null';
3848
- if (typeof value === 'undefined') return 'undefined';
3849
- return value;
3850
3986
  }
3851
3987
  }
3852
3988
  });
@@ -4398,7 +4534,11 @@ requireComponents.keys().forEach((filePath) => {
4398
4534
  // Check if the file name matches the directory name
4399
4535
  if (directoryName === fileName) {
4400
4536
  components[directoryName] = requireComponents(filePath);
4401
- components[directoryName](app);
4537
+ if (typeof components[directoryName] === 'function') {
4538
+ components[directoryName](app);
4539
+ } else {
4540
+ app.component(directoryName, components[directoryName]);
4541
+ }
4402
4542
  }
4403
4543
  });
4404
4544
 
@@ -4456,18 +4596,21 @@ app.component('app-component', {
4456
4596
  return;
4457
4597
  }
4458
4598
 
4599
+ window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
4600
+
4459
4601
  try {
4460
4602
  const { nodeEnv } = await api.status();
4461
4603
  this.nodeEnv = nodeEnv;
4462
4604
  } catch (err) {
4463
4605
  this.authError = 'Error connecting to Mongoose Studio API: ' + err.response?.data?.message ?? err.message;
4464
4606
  this.status = 'loaded';
4607
+ window.localStorage.setItem('_mongooseStudioAccessToken', '');
4465
4608
  return;
4466
4609
  }
4467
4610
 
4468
4611
  this.user = user;
4469
4612
  this.roles = roles;
4470
- window.localStorage.setItem('_mongooseStudioAccessToken', accessToken._id);
4613
+
4471
4614
  setTimeout(() => {
4472
4615
  this.$router.replace(this.$router.currentRoute.value.path);
4473
4616
  }, 0);
@@ -6293,12 +6436,21 @@ client.interceptors.request.use(req => {
6293
6436
  return req;
6294
6437
  });
6295
6438
 
6439
+ function sanitizedReturnUrl() {
6440
+ const url = new URL(window.location.href);
6441
+ if (url.hash && url.hash.includes('?')) {
6442
+ const [hashPath, hashSearch] = url.hash.split('?', 2);
6443
+ url.hash = hashPath;
6444
+ }
6445
+ return url.toString();
6446
+ }
6447
+
6296
6448
  exports.githubLogin = function githubLogin() {
6297
- return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
6449
+ return client.post('/githubLogin', { state: sanitizedReturnUrl() }).then(res => res.data);
6298
6450
  };
6299
6451
 
6300
6452
  exports.googleLogin = function googleLogin() {
6301
- return client.post('/googleLogin', { state: window.location.href }).then(res => res.data);
6453
+ return client.post('/googleLogin', { state: sanitizedReturnUrl() }).then(res => res.data);
6302
6454
  };
6303
6455
 
6304
6456
  exports.getWorkspaceTeam = function getWorkspaceTeam() {
@@ -6937,7 +7089,7 @@ __webpack_require__.r(__webpack_exports__);
6937
7089
  /* harmony export */ });
6938
7090
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
6939
7091
  /**
6940
- * @vue/reactivity v3.5.26
7092
+ * @vue/reactivity v3.5.27
6941
7093
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
6942
7094
  * @license MIT
6943
7095
  **/
@@ -8066,20 +8218,20 @@ function createIterableMethod(method, isReadonly2, isShallow2) {
8066
8218
  "iterate",
8067
8219
  isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
8068
8220
  );
8069
- return {
8070
- // iterator protocol
8071
- next() {
8072
- const { value, done } = innerIterator.next();
8073
- return done ? { value, done } : {
8074
- value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
8075
- done
8076
- };
8077
- },
8078
- // iterable protocol
8079
- [Symbol.iterator]() {
8080
- return this;
8221
+ return (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.extend)(
8222
+ // inheriting all iterator properties
8223
+ Object.create(innerIterator),
8224
+ {
8225
+ // iterator protocol
8226
+ next() {
8227
+ const { value, done } = innerIterator.next();
8228
+ return done ? { value, done } : {
8229
+ value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
8230
+ done
8231
+ };
8232
+ }
8081
8233
  }
8082
- };
8234
+ );
8083
8235
  };
8084
8236
  }
8085
8237
  function createReadonlyMethod(type) {
@@ -8293,8 +8445,9 @@ function targetTypeMap(rawType) {
8293
8445
  function getTargetType(value) {
8294
8446
  return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.toRawType)(value));
8295
8447
  }
8448
+ // @__NO_SIDE_EFFECTS__
8296
8449
  function reactive(target) {
8297
- if (isReadonly(target)) {
8450
+ if (/* @__PURE__ */ isReadonly(target)) {
8298
8451
  return target;
8299
8452
  }
8300
8453
  return createReactiveObject(
@@ -8305,6 +8458,7 @@ function reactive(target) {
8305
8458
  reactiveMap
8306
8459
  );
8307
8460
  }
8461
+ // @__NO_SIDE_EFFECTS__
8308
8462
  function shallowReactive(target) {
8309
8463
  return createReactiveObject(
8310
8464
  target,
@@ -8314,6 +8468,7 @@ function shallowReactive(target) {
8314
8468
  shallowReactiveMap
8315
8469
  );
8316
8470
  }
8471
+ // @__NO_SIDE_EFFECTS__
8317
8472
  function readonly(target) {
8318
8473
  return createReactiveObject(
8319
8474
  target,
@@ -8323,6 +8478,7 @@ function readonly(target) {
8323
8478
  readonlyMap
8324
8479
  );
8325
8480
  }
8481
+ // @__NO_SIDE_EFFECTS__
8326
8482
  function shallowReadonly(target) {
8327
8483
  return createReactiveObject(
8328
8484
  target,
@@ -8361,24 +8517,29 @@ function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandl
8361
8517
  proxyMap.set(target, proxy);
8362
8518
  return proxy;
8363
8519
  }
8520
+ // @__NO_SIDE_EFFECTS__
8364
8521
  function isReactive(value) {
8365
- if (isReadonly(value)) {
8366
- return isReactive(value["__v_raw"]);
8522
+ if (/* @__PURE__ */ isReadonly(value)) {
8523
+ return /* @__PURE__ */ isReactive(value["__v_raw"]);
8367
8524
  }
8368
8525
  return !!(value && value["__v_isReactive"]);
8369
8526
  }
8527
+ // @__NO_SIDE_EFFECTS__
8370
8528
  function isReadonly(value) {
8371
8529
  return !!(value && value["__v_isReadonly"]);
8372
8530
  }
8531
+ // @__NO_SIDE_EFFECTS__
8373
8532
  function isShallow(value) {
8374
8533
  return !!(value && value["__v_isShallow"]);
8375
8534
  }
8535
+ // @__NO_SIDE_EFFECTS__
8376
8536
  function isProxy(value) {
8377
8537
  return value ? !!value["__v_raw"] : false;
8378
8538
  }
8539
+ // @__NO_SIDE_EFFECTS__
8379
8540
  function toRaw(observed) {
8380
8541
  const raw = observed && observed["__v_raw"];
8381
- return raw ? toRaw(raw) : observed;
8542
+ return raw ? /* @__PURE__ */ toRaw(raw) : observed;
8382
8543
  }
8383
8544
  function markRaw(value) {
8384
8545
  if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.hasOwn)(value, "__v_skip") && Object.isExtensible(value)) {
@@ -8386,20 +8547,23 @@ function markRaw(value) {
8386
8547
  }
8387
8548
  return value;
8388
8549
  }
8389
- const toReactive = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? reactive(value) : value;
8390
- const toReadonly = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? readonly(value) : value;
8550
+ const toReactive = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? /* @__PURE__ */ reactive(value) : value;
8551
+ const toReadonly = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? /* @__PURE__ */ readonly(value) : value;
8391
8552
 
8553
+ // @__NO_SIDE_EFFECTS__
8392
8554
  function isRef(r) {
8393
8555
  return r ? r["__v_isRef"] === true : false;
8394
8556
  }
8557
+ // @__NO_SIDE_EFFECTS__
8395
8558
  function ref(value) {
8396
8559
  return createRef(value, false);
8397
8560
  }
8561
+ // @__NO_SIDE_EFFECTS__
8398
8562
  function shallowRef(value) {
8399
8563
  return createRef(value, true);
8400
8564
  }
8401
8565
  function createRef(rawValue, shallow) {
8402
- if (isRef(rawValue)) {
8566
+ if (/* @__PURE__ */ isRef(rawValue)) {
8403
8567
  return rawValue;
8404
8568
  }
8405
8569
  return new RefImpl(rawValue, shallow);
@@ -8458,7 +8622,7 @@ function triggerRef(ref2) {
8458
8622
  }
8459
8623
  }
8460
8624
  function unref(ref2) {
8461
- return isRef(ref2) ? ref2.value : ref2;
8625
+ return /* @__PURE__ */ isRef(ref2) ? ref2.value : ref2;
8462
8626
  }
8463
8627
  function toValue(source) {
8464
8628
  return (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isFunction)(source) ? source() : unref(source);
@@ -8467,7 +8631,7 @@ const shallowUnwrapHandlers = {
8467
8631
  get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)),
8468
8632
  set: (target, key, value, receiver) => {
8469
8633
  const oldValue = target[key];
8470
- if (isRef(oldValue) && !isRef(value)) {
8634
+ if (/* @__PURE__ */ isRef(oldValue) && !/* @__PURE__ */ isRef(value)) {
8471
8635
  oldValue.value = value;
8472
8636
  return true;
8473
8637
  } else {
@@ -8497,6 +8661,7 @@ class CustomRefImpl {
8497
8661
  function customRef(factory) {
8498
8662
  return new CustomRefImpl(factory);
8499
8663
  }
8664
+ // @__NO_SIDE_EFFECTS__
8500
8665
  function toRefs(object) {
8501
8666
  if ( true && !isProxy(object)) {
8502
8667
  warn(`toRefs() expects a reactive object but received a plain one.`);
@@ -8532,9 +8697,9 @@ class ObjectRefImpl {
8532
8697
  return this._value = val === void 0 ? this._defaultValue : val;
8533
8698
  }
8534
8699
  set value(newVal) {
8535
- if (this._shallow && isRef(this._raw[this._key])) {
8700
+ if (this._shallow && /* @__PURE__ */ isRef(this._raw[this._key])) {
8536
8701
  const nestedRef = this._object[this._key];
8537
- if (isRef(nestedRef)) {
8702
+ if (/* @__PURE__ */ isRef(nestedRef)) {
8538
8703
  nestedRef.value = newVal;
8539
8704
  return;
8540
8705
  }
@@ -8556,15 +8721,16 @@ class GetterRefImpl {
8556
8721
  return this._value = this._getter();
8557
8722
  }
8558
8723
  }
8724
+ // @__NO_SIDE_EFFECTS__
8559
8725
  function toRef(source, key, defaultValue) {
8560
- if (isRef(source)) {
8726
+ if (/* @__PURE__ */ isRef(source)) {
8561
8727
  return source;
8562
8728
  } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isFunction)(source)) {
8563
8729
  return new GetterRefImpl(source);
8564
8730
  } else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(source) && arguments.length > 1) {
8565
8731
  return propertyToRef(source, key, defaultValue);
8566
8732
  } else {
8567
- return ref(source);
8733
+ return /* @__PURE__ */ ref(source);
8568
8734
  }
8569
8735
  }
8570
8736
  function propertyToRef(source, key, defaultValue) {
@@ -8645,6 +8811,7 @@ class ComputedRefImpl {
8645
8811
  }
8646
8812
  }
8647
8813
  }
8814
+ // @__NO_SIDE_EFFECTS__
8648
8815
  function computed(getterOrOptions, debugOptions, isSSR = false) {
8649
8816
  let getter;
8650
8817
  let setter;
@@ -9060,7 +9227,7 @@ __webpack_require__.r(__webpack_exports__);
9060
9227
  /* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
9061
9228
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
9062
9229
  /**
9063
- * @vue/runtime-core v3.5.26
9230
+ * @vue/runtime-core v3.5.27
9064
9231
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
9065
9232
  * @license MIT
9066
9233
  **/
@@ -11211,7 +11378,7 @@ Server rendered element contains more child nodes than client vdom.`
11211
11378
  logMismatchError();
11212
11379
  }
11213
11380
  if (forcePatch && (key.endsWith("value") || key === "indeterminate") || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isOn)(key) && !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isReservedProp)(key) || // force hydrate v-bind with .prop modifiers
11214
- key[0] === "." || isCustomElement) {
11381
+ key[0] === "." || isCustomElement && !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isReservedProp)(key)) {
11215
11382
  patchProp(el, key, null, props[key], void 0, parentComponent);
11216
11383
  }
11217
11384
  }
@@ -17559,7 +17726,7 @@ function isMemoSame(cached, memo) {
17559
17726
  return true;
17560
17727
  }
17561
17728
 
17562
- const version = "3.5.26";
17729
+ const version = "3.5.27";
17563
17730
  const warn = true ? warn$1 : 0;
17564
17731
  const ErrorTypeStrings = ErrorTypeStrings$1 ;
17565
17732
  const devtools = true ? devtools$1 : 0;
@@ -17770,7 +17937,7 @@ __webpack_require__.r(__webpack_exports__);
17770
17937
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
17771
17938
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
17772
17939
  /**
17773
- * @vue/runtime-dom v3.5.26
17940
+ * @vue/runtime-dom v3.5.27
17774
17941
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
17775
17942
  * @license MIT
17776
17943
  **/
@@ -19776,7 +19943,7 @@ __webpack_require__.r(__webpack_exports__);
19776
19943
  /* harmony export */ toTypeString: () => (/* binding */ toTypeString)
19777
19944
  /* harmony export */ });
19778
19945
  /**
19779
- * @vue/shared v3.5.26
19946
+ * @vue/shared v3.5.27
19780
19947
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
19781
19948
  * @license MIT
19782
19949
  **/
@@ -23500,6 +23667,7 @@ __webpack_require__.r(__webpack_exports__);
23500
23667
  /* harmony export */ BSONValue: () => (/* binding */ BSONValue),
23501
23668
  /* harmony export */ BSONVersionError: () => (/* binding */ BSONVersionError),
23502
23669
  /* harmony export */ Binary: () => (/* binding */ Binary),
23670
+ /* harmony export */ ByteUtils: () => (/* binding */ ByteUtils),
23503
23671
  /* harmony export */ Code: () => (/* binding */ Code),
23504
23672
  /* harmony export */ DBRef: () => (/* binding */ DBRef),
23505
23673
  /* harmony export */ Decimal128: () => (/* binding */ Decimal128),
@@ -23509,6 +23677,7 @@ __webpack_require__.r(__webpack_exports__);
23509
23677
  /* harmony export */ Long: () => (/* binding */ Long),
23510
23678
  /* harmony export */ MaxKey: () => (/* binding */ MaxKey),
23511
23679
  /* harmony export */ MinKey: () => (/* binding */ MinKey),
23680
+ /* harmony export */ NumberUtils: () => (/* binding */ NumberUtils),
23512
23681
  /* harmony export */ ObjectId: () => (/* binding */ ObjectId),
23513
23682
  /* harmony export */ Timestamp: () => (/* binding */ Timestamp),
23514
23683
  /* harmony export */ UUID: () => (/* binding */ UUID),
@@ -23753,6 +23922,7 @@ const nodejsRandomBytes = (() => {
23753
23922
  }
23754
23923
  })();
23755
23924
  const nodeJsByteUtils = {
23925
+ isUint8Array: isUint8Array,
23756
23926
  toLocalBufferType(potentialBuffer) {
23757
23927
  if (Buffer.isBuffer(potentialBuffer)) {
23758
23928
  return potentialBuffer;
@@ -23775,6 +23945,12 @@ const nodeJsByteUtils = {
23775
23945
  allocateUnsafe(size) {
23776
23946
  return Buffer.allocUnsafe(size);
23777
23947
  },
23948
+ compare(a, b) {
23949
+ return nodeJsByteUtils.toLocalBufferType(a).compare(b);
23950
+ },
23951
+ concat(list) {
23952
+ return Buffer.concat(list);
23953
+ },
23778
23954
  equals(a, b) {
23779
23955
  return nodeJsByteUtils.toLocalBufferType(a).equals(b);
23780
23956
  },
@@ -23784,6 +23960,9 @@ const nodeJsByteUtils = {
23784
23960
  fromBase64(base64) {
23785
23961
  return Buffer.from(base64, 'base64');
23786
23962
  },
23963
+ fromUTF8(utf8) {
23964
+ return Buffer.from(utf8, 'utf8');
23965
+ },
23787
23966
  toBase64(buffer) {
23788
23967
  return nodeJsByteUtils.toLocalBufferType(buffer).toString('base64');
23789
23968
  },
@@ -23858,6 +24037,7 @@ const webRandomBytes = (() => {
23858
24037
  })();
23859
24038
  const HEX_DIGIT = /(\d|[a-f])/i;
23860
24039
  const webByteUtils = {
24040
+ isUint8Array: isUint8Array,
23861
24041
  toLocalBufferType(potentialUint8array) {
23862
24042
  const stringTag = potentialUint8array?.[Symbol.toStringTag] ??
23863
24043
  Object.prototype.toString.call(potentialUint8array);
@@ -23884,12 +24064,43 @@ const webByteUtils = {
23884
24064
  allocateUnsafe(size) {
23885
24065
  return webByteUtils.allocate(size);
23886
24066
  },
23887
- equals(a, b) {
23888
- if (a.byteLength !== b.byteLength) {
24067
+ compare(uint8Array, otherUint8Array) {
24068
+ if (uint8Array === otherUint8Array)
24069
+ return 0;
24070
+ const len = Math.min(uint8Array.length, otherUint8Array.length);
24071
+ for (let i = 0; i < len; i++) {
24072
+ if (uint8Array[i] < otherUint8Array[i])
24073
+ return -1;
24074
+ if (uint8Array[i] > otherUint8Array[i])
24075
+ return 1;
24076
+ }
24077
+ if (uint8Array.length < otherUint8Array.length)
24078
+ return -1;
24079
+ if (uint8Array.length > otherUint8Array.length)
24080
+ return 1;
24081
+ return 0;
24082
+ },
24083
+ concat(uint8Arrays) {
24084
+ if (uint8Arrays.length === 0)
24085
+ return webByteUtils.allocate(0);
24086
+ let totalLength = 0;
24087
+ for (const uint8Array of uint8Arrays) {
24088
+ totalLength += uint8Array.length;
24089
+ }
24090
+ const result = webByteUtils.allocate(totalLength);
24091
+ let offset = 0;
24092
+ for (const uint8Array of uint8Arrays) {
24093
+ result.set(uint8Array, offset);
24094
+ offset += uint8Array.length;
24095
+ }
24096
+ return result;
24097
+ },
24098
+ equals(uint8Array, otherUint8Array) {
24099
+ if (uint8Array.byteLength !== otherUint8Array.byteLength) {
23889
24100
  return false;
23890
24101
  }
23891
- for (let i = 0; i < a.byteLength; i++) {
23892
- if (a[i] !== b[i]) {
24102
+ for (let i = 0; i < uint8Array.byteLength; i++) {
24103
+ if (uint8Array[i] !== otherUint8Array[i]) {
23893
24104
  return false;
23894
24105
  }
23895
24106
  }
@@ -23901,6 +24112,9 @@ const webByteUtils = {
23901
24112
  fromBase64(base64) {
23902
24113
  return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
23903
24114
  },
24115
+ fromUTF8(utf8) {
24116
+ return new TextEncoder().encode(utf8);
24117
+ },
23904
24118
  toBase64(uint8array) {
23905
24119
  return btoa(webByteUtils.toISO88591(uint8array));
23906
24120
  },
@@ -28136,6 +28350,7 @@ var bson = /*#__PURE__*/Object.freeze({
28136
28350
  BSONValue: BSONValue,
28137
28351
  BSONVersionError: BSONVersionError,
28138
28352
  Binary: Binary,
28353
+ ByteUtils: ByteUtils,
28139
28354
  Code: Code,
28140
28355
  DBRef: DBRef,
28141
28356
  Decimal128: Decimal128,
@@ -28145,6 +28360,7 @@ var bson = /*#__PURE__*/Object.freeze({
28145
28360
  Long: Long,
28146
28361
  MaxKey: MaxKey,
28147
28362
  MinKey: MinKey,
28363
+ NumberUtils: NumberUtils,
28148
28364
  ObjectId: ObjectId,
28149
28365
  Timestamp: Timestamp,
28150
28366
  UUID: UUID,
@@ -32198,7 +32414,7 @@ __webpack_require__.r(__webpack_exports__);
32198
32414
  /* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js");
32199
32415
  /* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js");
32200
32416
  /**
32201
- * vue v3.5.26
32417
+ * vue v3.5.27
32202
32418
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
32203
32419
  * @license MIT
32204
32420
  **/
@@ -32234,7 +32450,7 @@ const compile = () => {
32234
32450
  (module) {
32235
32451
 
32236
32452
  "use strict";
32237
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.1","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
32453
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.3","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
32238
32454
 
32239
32455
  /***/ }
32240
32456