@mongoosejs/studio 0.5.13 → 0.6.0

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.
Files changed (86) hide show
  1. package/AGENTS.md +74 -0
  2. package/README.md +27 -0
  3. package/backend/actions/ChatMessage/executeScript.js +15 -5
  4. package/backend/actions/ChatThread/createChatMessage.js +7 -2
  5. package/backend/actions/ChatThread/getChatThread.js +8 -2
  6. package/backend/actions/ChatThread/listChatThreads.js +6 -1
  7. package/backend/actions/ChatThread/shareChatThread.js +8 -2
  8. package/backend/actions/ChatThread/streamChatMessage.js +7 -2
  9. package/backend/actions/ChatThread/toggleAgentMode.js +8 -2
  10. package/backend/actions/Dashboard/createDashboard.js +3 -0
  11. package/backend/actions/Dashboard/deleteDashboard.js +13 -3
  12. package/backend/actions/Dashboard/getDashboard.js +33 -10
  13. package/backend/actions/Dashboard/getDashboards.js +9 -1
  14. package/backend/actions/Dashboard/updateDashboard.js +9 -2
  15. package/backend/actions/Model/addField.js +9 -2
  16. package/backend/actions/Model/analyzeSchema.js +16 -4
  17. package/backend/actions/Model/createChatMessage.js +3 -0
  18. package/backend/actions/Model/createDocument.js +3 -0
  19. package/backend/actions/Model/deleteDocument.js +9 -3
  20. package/backend/actions/Model/deleteDocuments.js +9 -1
  21. package/backend/actions/Model/dropCollection.js +3 -0
  22. package/backend/actions/Model/dropIndex.js +3 -0
  23. package/backend/actions/Model/executeDocumentScript.js +33 -26
  24. package/backend/actions/Model/exportQueryResults.js +13 -1
  25. package/backend/actions/Model/getCollectionInfo.js +15 -3
  26. package/backend/actions/Model/getDocument.js +13 -2
  27. package/backend/actions/Model/getDocuments.js +25 -5
  28. package/backend/actions/Model/getDocumentsStream.js +38 -18
  29. package/backend/actions/Model/getEstimatedDocumentCounts.js +15 -2
  30. package/backend/actions/Model/getIndexes.js +3 -0
  31. package/backend/actions/Model/getSuggestedProjection.js +3 -0
  32. package/backend/actions/Model/listModels.js +3 -0
  33. package/backend/actions/Model/streamChatMessage.js +3 -0
  34. package/backend/actions/Model/streamDocumentChanges.js +3 -0
  35. package/backend/actions/Model/updateDocument.js +10 -2
  36. package/backend/actions/Model/updateDocuments.js +10 -2
  37. package/backend/actions/Model/validateDocument.js +15 -2
  38. package/backend/actions/Script/createScript.js +36 -0
  39. package/backend/actions/Script/executeScript.js +89 -0
  40. package/backend/actions/Script/getScript.js +43 -0
  41. package/backend/actions/Script/index.js +5 -0
  42. package/backend/actions/Task/cancelTask.js +9 -3
  43. package/backend/actions/Task/getTaskOverview.js +7 -3
  44. package/backend/actions/Task/getTasks.js +7 -3
  45. package/backend/actions/Task/getTasksOverTime.js +7 -3
  46. package/backend/actions/Task/rescheduleTask.js +9 -3
  47. package/backend/actions/Task/runTask.js +9 -3
  48. package/backend/actions/index.js +1 -0
  49. package/backend/authorize.js +6 -1
  50. package/backend/db/scriptSchema.js +29 -0
  51. package/backend/helpers/omitNullish.js +5 -0
  52. package/backend/helpers/operationOptions.js +26 -0
  53. package/backend/helpers/readOperationOptions.js +31 -0
  54. package/backend/index.js +6 -4
  55. package/backend/integrations/mothership.js +28 -0
  56. package/backend/mcp.js +140 -0
  57. package/backend/netlify.js +5 -3
  58. package/backend/next.js +5 -3
  59. package/backend/sandbox/createSandbox.js +2 -2
  60. package/backend/sandbox/createScriptDb.js +60 -20
  61. package/backend/util/archetypeToZodSchema.js +74 -0
  62. package/docs/mcp-oauth.md +106 -0
  63. package/eslint.config.js +1 -0
  64. package/eval/agent/run.js +1 -1
  65. package/express.js +183 -41
  66. package/frontend/index.js +3 -0
  67. package/frontend/public/app.js +740 -124
  68. package/frontend/public/tw.css +76 -0
  69. package/frontend/src/api.js +27 -1
  70. package/frontend/src/index.js +9 -1
  71. package/frontend/src/mcp/mcp.html +55 -0
  72. package/frontend/src/mcp/mcp.js +27 -0
  73. package/frontend/src/models/models.html +29 -8
  74. package/frontend/src/models/models.js +30 -2
  75. package/frontend/src/navbar/navbar.html +8 -0
  76. package/frontend/src/navbar/navbar.js +3 -0
  77. package/frontend/src/routes.js +16 -0
  78. package/frontend/src/script/script.html +105 -0
  79. package/frontend/src/script/script.js +98 -0
  80. package/frontend/src/tooltip/tooltip.html +28 -0
  81. package/frontend/src/tooltip/tooltip.js +94 -0
  82. package/index.d.ts +10 -1
  83. package/local.js +2 -1
  84. package/nest.d.ts +4 -0
  85. package/package.json +5 -3
  86. package/vercel.d.ts +4 -0
@@ -1204,6 +1204,17 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
1204
1204
  return client.post('', { action: 'ChatMessage.executeScript', ...params }).then(res => res.data);
1205
1205
  }
1206
1206
  };
1207
+ exports.Script = {
1208
+ createScript(params) {
1209
+ return client.post('', { action: 'Script.createScript', ...params }).then(res => res.data);
1210
+ },
1211
+ executeScript(params) {
1212
+ return client.post('', { action: 'Script.executeScript', ...params }).then(res => res.data);
1213
+ },
1214
+ getScript(params) {
1215
+ return client.post('', { action: 'Script.getScript', ...params }).then(res => res.data);
1216
+ }
1217
+ };
1207
1218
  exports.Model = {
1208
1219
  addField(params) {
1209
1220
  return client.post('', { action: 'Model.addField', ...params }).then(res => res.data);
@@ -1269,7 +1280,11 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
1269
1280
  getDocumentsStream: async function* getDocumentsStream(params) {
1270
1281
  const data = await client.post('', { action: 'Model.getDocuments', ...params }).then(res => res.data);
1271
1282
  yield { schemaPaths: data.schemaPaths, suggestedFields: data.suggestedFields };
1272
- yield { numDocs: data.numDocs };
1283
+ if (data.numDocsError) {
1284
+ yield { numDocsError: data.numDocsError };
1285
+ } else {
1286
+ yield { numDocs: data.numDocs };
1287
+ }
1273
1288
  for (const doc of data.docs) {
1274
1289
  yield { document: doc };
1275
1290
  }
@@ -1399,6 +1414,17 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
1399
1414
  return client.post('/ChatMessage/executeScript', params).then(res => res.data);
1400
1415
  }
1401
1416
  };
1417
+ exports.Script = {
1418
+ createScript: function createScript(params) {
1419
+ return client.post('/Script/createScript', params).then(res => res.data);
1420
+ },
1421
+ executeScript: function executeScript(params) {
1422
+ return client.post('/Script/executeScript', params).then(res => res.data);
1423
+ },
1424
+ getScript: function getScript(params) {
1425
+ return client.post('/Script/getScript', params).then(res => res.data);
1426
+ }
1427
+ };
1402
1428
  exports.Model = {
1403
1429
  addField(params) {
1404
1430
  return client.post('/Model/addField', params).then(res => res.data);
@@ -7847,7 +7873,7 @@ const {
7847
7873
  } = __webpack_require__(/*! ./_util/recent-pages-history */ "./frontend/src/_util/recent-pages-history.js");
7848
7874
  appendCSS(__webpack_require__(/*! vue-toastification/dist/index.css */ "./node_modules/vue-toastification/dist/index.css"));
7849
7875
 
7850
- const TRACKED_RECENT_PAGE_ROUTE_NAMES = new Set(['model', 'document', 'dashboard', 'chat']);
7876
+ const TRACKED_RECENT_PAGE_ROUTE_NAMES = new Set(['model', 'document', 'dashboard', 'chat', 'script']);
7851
7877
  const CHAT_ROUTE_NAMES = new Set(['chat index', 'chat']);
7852
7878
 
7853
7879
  function formatHistoryLabel(route) {
@@ -7869,6 +7895,9 @@ function formatHistoryLabel(route) {
7869
7895
  if (route.name === 'chat') {
7870
7896
  return route.params?.threadId ? `Chat: ${route.params.threadId}` : 'Chat';
7871
7897
  }
7898
+ if (route.name === 'script') {
7899
+ return route.params?.scriptId ? `Script: ${route.params.scriptId}` : 'Script';
7900
+ }
7872
7901
  const normalizedPath = route.path.replace(/^\//, '');
7873
7902
  return normalizedPath || 'Home';
7874
7903
  }
@@ -8156,6 +8185,11 @@ const router = VueRouter.createRouter({
8156
8185
 
8157
8186
  // Add global navigation guard
8158
8187
  router.beforeEach((to, from, next) => {
8188
+ if (to.name === 'mcp' && window.MONGOOSE_STUDIO_CONFIG.mcp === false) {
8189
+ next({ name: window.state?.roles?.[0] === 'dashboards' ? 'dashboards' : 'root' });
8190
+ return;
8191
+ }
8192
+
8159
8193
  // Skip auth check for authorized (public) routes
8160
8194
  if (to.meta.authorized) {
8161
8195
  next();
@@ -8884,6 +8918,44 @@ module.exports = app => app.component('list-subdocument', {
8884
8918
  });
8885
8919
 
8886
8920
 
8921
+ /***/ },
8922
+
8923
+ /***/ "./frontend/src/mcp/mcp.js"
8924
+ /*!*********************************!*\
8925
+ !*** ./frontend/src/mcp/mcp.js ***!
8926
+ \*********************************/
8927
+ (module, __unused_webpack_exports, __webpack_require__) {
8928
+
8929
+ "use strict";
8930
+
8931
+
8932
+ const template = __webpack_require__(/*! ./mcp.html */ "./frontend/src/mcp/mcp.html");
8933
+
8934
+ module.exports = {
8935
+ template,
8936
+ computed: {
8937
+ mcpUrl() {
8938
+ const configuredUrl = window.MONGOOSE_STUDIO_CONFIG.mcpUrl;
8939
+ if (configuredUrl) {
8940
+ return configuredUrl;
8941
+ }
8942
+
8943
+ const url = new URL(window.location.href);
8944
+ url.hash = '';
8945
+ url.search = '';
8946
+ url.pathname = `${url.pathname.replace(/\/+$/, '')}/mcp`;
8947
+ return url.toString();
8948
+ }
8949
+ },
8950
+ methods: {
8951
+ async copyMCPUrl() {
8952
+ await navigator.clipboard.writeText(this.mcpUrl);
8953
+ this.$toast.success('MCP URL copied!');
8954
+ }
8955
+ }
8956
+ };
8957
+
8958
+
8887
8959
  /***/ },
8888
8960
 
8889
8961
  /***/ "./frontend/src/modal/modal.js"
@@ -9621,6 +9693,8 @@ module.exports = app => app.component('models', {
9621
9693
  filteredPaths: [],
9622
9694
  selectedPaths: [],
9623
9695
  numDocuments: null,
9696
+ numDocumentsError: null,
9697
+ documentsError: null,
9624
9698
  mongoDBIndexes: [],
9625
9699
  schemaIndexes: [],
9626
9700
  status: 'loading',
@@ -10308,7 +10382,8 @@ module.exports = app => app.component('models', {
10308
10382
  return filteredDoc;
10309
10383
  },
10310
10384
  async checkIfScrolledToBottom() {
10311
- if (this.status === 'loading' || this.loadedAllDocs) {
10385
+ // Stop infinite scroll if the last load failed, otherwise we would retry the same failing request forever.
10386
+ if (this.status === 'loading' || this.loadedAllDocs || this.documentsError) {
10312
10387
  return;
10313
10388
  }
10314
10389
  // Infinite scroll only applies to table/json views.
@@ -10334,11 +10409,20 @@ module.exports = app => app.component('models', {
10334
10409
  try {
10335
10410
  const skip = this.documents.length;
10336
10411
  const params = this.buildDocumentFetchParams({ skip });
10337
- const { docs } = await api.Model.getDocuments(params);
10412
+ const { docs, numDocs, numDocsError } = await api.Model.getDocuments(params);
10413
+ if (numDocsError) {
10414
+ this.numDocumentsError = numDocsError;
10415
+ } else if (numDocs !== undefined) {
10416
+ this.numDocuments = numDocs;
10417
+ this.numDocumentsError = null;
10418
+ }
10338
10419
  if (docs.length < limit) {
10339
10420
  this.loadedAllDocs = true;
10340
10421
  }
10341
10422
  this.documents.push(...docs);
10423
+ this.documentsError = null;
10424
+ } catch (err) {
10425
+ this.documentsError = err?.message;
10342
10426
  } finally {
10343
10427
  this.loadingMore = false;
10344
10428
  this.status = 'loaded';
@@ -10551,6 +10635,8 @@ module.exports = app => app.component('models', {
10551
10635
  // Clear previous data
10552
10636
  this.documents = [];
10553
10637
  this.numDocuments = null;
10638
+ this.numDocumentsError = null;
10639
+ this.documentsError = null;
10554
10640
  this.loadedAllDocs = false;
10555
10641
  this.lastSelectedIndex = null;
10556
10642
 
@@ -10600,6 +10686,10 @@ module.exports = app => app.component('models', {
10600
10686
  }
10601
10687
  if (event.numDocs !== undefined) {
10602
10688
  this.numDocuments = event.numDocs;
10689
+ this.numDocumentsError = null;
10690
+ }
10691
+ if (event.numDocsError) {
10692
+ this.numDocumentsError = event.numDocsError;
10603
10693
  }
10604
10694
  if (event.document) {
10605
10695
  this.documents.push(event.document);
@@ -10613,6 +10703,9 @@ module.exports = app => app.component('models', {
10613
10703
  if (docsCount < limit) {
10614
10704
  this.loadedAllDocs = true;
10615
10705
  }
10706
+ this.documentsError = null;
10707
+ } catch (err) {
10708
+ this.documentsError = err?.message;
10616
10709
  } finally {
10617
10710
  this.status = 'loaded';
10618
10711
  }
@@ -10639,8 +10732,12 @@ module.exports = app => app.component('models', {
10639
10732
  for await (const event of api.Model.getDocumentsStream(params)) {
10640
10733
  if (event.numDocs !== undefined && !numDocsReceived) {
10641
10734
  this.numDocuments = event.numDocs;
10735
+ this.numDocumentsError = null;
10642
10736
  numDocsReceived = true;
10643
10737
  }
10738
+ if (event.numDocsError) {
10739
+ this.numDocumentsError = event.numDocsError;
10740
+ }
10644
10741
  if (event.document) {
10645
10742
  this.documents.push(event.document);
10646
10743
  docsCount++;
@@ -10653,6 +10750,9 @@ module.exports = app => app.component('models', {
10653
10750
  if (docsCount < limit) {
10654
10751
  this.loadedAllDocs = true;
10655
10752
  }
10753
+ this.documentsError = null;
10754
+ } catch (err) {
10755
+ this.documentsError = err?.message;
10656
10756
  } finally {
10657
10757
  this.loadingMore = false;
10658
10758
  this.status = 'loaded';
@@ -11707,6 +11807,9 @@ module.exports = app => app.component('navbar', {
11707
11807
  hasTaskVisualizer() {
11708
11808
  return !!window.MONGOOSE_STUDIO_CONFIG.enableTaskVisualizer;
11709
11809
  },
11810
+ mcpEnabled() {
11811
+ return window.MONGOOSE_STUDIO_CONFIG.mcp !== false;
11812
+ },
11710
11813
  recentPagesList() {
11711
11814
  return this.localRecentPages;
11712
11815
  },
@@ -11906,6 +12009,14 @@ module.exports = {
11906
12009
  authorized: false
11907
12010
  }
11908
12011
  },
12012
+ {
12013
+ path: '/mcp',
12014
+ name: 'mcp',
12015
+ component: 'mcp',
12016
+ meta: {
12017
+ authorized: true
12018
+ }
12019
+ },
11909
12020
  {
11910
12021
  path: '/tasks',
11911
12022
  name: 'tasks',
@@ -11945,6 +12056,14 @@ module.exports = {
11945
12056
  meta: {
11946
12057
  authorized: true
11947
12058
  }
12059
+ },
12060
+ {
12061
+ path: '/script/:scriptId',
12062
+ name: 'script',
12063
+ component: 'script',
12064
+ meta: {
12065
+ authorized: true
12066
+ }
11948
12067
  }
11949
12068
  ],
11950
12069
  roleAccess,
@@ -11952,6 +12071,115 @@ module.exports = {
11952
12071
  };
11953
12072
 
11954
12073
 
12074
+ /***/ },
12075
+
12076
+ /***/ "./frontend/src/script/script.js"
12077
+ /*!***************************************!*\
12078
+ !*** ./frontend/src/script/script.js ***!
12079
+ \***************************************/
12080
+ (module, __unused_webpack_exports, __webpack_require__) {
12081
+
12082
+ "use strict";
12083
+ /* global Prism */
12084
+
12085
+
12086
+ const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
12087
+ const baseComponent = __webpack_require__(/*! ../_util/baseComponent */ "./frontend/src/_util/baseComponent.js");
12088
+ const template = __webpack_require__(/*! ./script.html */ "./frontend/src/script/script.html");
12089
+
12090
+ module.exports = {
12091
+ template,
12092
+ extends: baseComponent,
12093
+ props: ['scriptId'],
12094
+ inject: ['state'],
12095
+ data() {
12096
+ return {
12097
+ status: 'loading',
12098
+ script: null,
12099
+ editedScript: '',
12100
+ isEditing: false,
12101
+ activeTab: 'code',
12102
+ selectedRunMode: 'run',
12103
+ showRunDropdown: false
12104
+ };
12105
+ },
12106
+ computed: {
12107
+ canUseDryRun() {
12108
+ return this.state.capabilities?.supportsTransactions !== false;
12109
+ },
12110
+ selectedRunLabel() {
12111
+ return this.selectedRunMode === 'dryRun' ? 'Dry Run' : 'Run';
12112
+ },
12113
+ isDryRunResult() {
12114
+ return this.script?.executionFinishedAt != null && this.script?.dryRun === true;
12115
+ }
12116
+ },
12117
+ methods: {
12118
+ async loadScript() {
12119
+ this.status = 'loading';
12120
+ try {
12121
+ const { script } = await api.Script.getScript({ scriptId: this.scriptId });
12122
+ this.script = script;
12123
+ this.editedScript = script.script;
12124
+ this.activeTab = script.executionResult == null ? 'code' : 'output';
12125
+ } finally {
12126
+ this.status = 'loaded';
12127
+ this.highlightCode();
12128
+ }
12129
+ },
12130
+ startEditing() {
12131
+ this.editedScript = this.script.script;
12132
+ this.isEditing = true;
12133
+ this.activeTab = 'code';
12134
+ },
12135
+ cancelEditing() {
12136
+ this.editedScript = this.script.script;
12137
+ this.isEditing = false;
12138
+ this.highlightCode();
12139
+ },
12140
+ selectRunMode(mode) {
12141
+ if (mode === 'dryRun' && !this.canUseDryRun) {
12142
+ return;
12143
+ }
12144
+ this.selectedRunMode = mode;
12145
+ this.showRunDropdown = false;
12146
+ },
12147
+ async executeScript() {
12148
+ this.showRunDropdown = false;
12149
+ try {
12150
+ const { script } = await api.Script.executeScript({
12151
+ scriptId: this.scriptId,
12152
+ script: this.isEditing ? this.editedScript : this.script.script,
12153
+ dryRun: this.selectedRunMode === 'dryRun'
12154
+ });
12155
+ this.script = script;
12156
+ this.editedScript = script.script;
12157
+ this.isEditing = false;
12158
+ this.activeTab = 'output';
12159
+ this.$toast.success(this.script.dryRun ? 'Dry run completed!' : 'Script executed successfully!');
12160
+ } catch (err) {
12161
+ const { script } = await api.Script.getScript({ scriptId: this.scriptId });
12162
+ this.script = script;
12163
+ this.editedScript = script.script;
12164
+ this.isEditing = false;
12165
+ this.activeTab = 'output';
12166
+ throw err;
12167
+ }
12168
+ },
12169
+ highlightCode() {
12170
+ this.$nextTick(() => {
12171
+ if (this.$refs.code) {
12172
+ Prism.highlightElement(this.$refs.code);
12173
+ }
12174
+ });
12175
+ }
12176
+ },
12177
+ mounted() {
12178
+ return this.loadScript();
12179
+ }
12180
+ };
12181
+
12182
+
11955
12183
  /***/ },
11956
12184
 
11957
12185
  /***/ "./frontend/src/splash/splash.js"
@@ -13175,6 +13403,111 @@ module.exports = app => app.component('team', {
13175
13403
  });
13176
13404
 
13177
13405
 
13406
+ /***/ },
13407
+
13408
+ /***/ "./frontend/src/tooltip/tooltip.js"
13409
+ /*!*****************************************!*\
13410
+ !*** ./frontend/src/tooltip/tooltip.js ***!
13411
+ \*****************************************/
13412
+ (module, __unused_webpack_exports, __webpack_require__) {
13413
+
13414
+ "use strict";
13415
+
13416
+
13417
+ const template = __webpack_require__(/*! ./tooltip.html */ "./frontend/src/tooltip/tooltip.html");
13418
+
13419
+ let nextTooltipId = 0;
13420
+
13421
+ module.exports = app => app.component('tooltip', {
13422
+ props: {
13423
+ text: {
13424
+ type: String,
13425
+ required: true
13426
+ }
13427
+ },
13428
+ template,
13429
+ data() {
13430
+ return {
13431
+ isOpen: false,
13432
+ isPinned: false,
13433
+ placement: 'bottom',
13434
+ tooltipId: `tooltip-${++nextTooltipId}`,
13435
+ tooltipStyle: {
13436
+ visibility: 'hidden'
13437
+ }
13438
+ };
13439
+ },
13440
+ mounted() {
13441
+ document.addEventListener('click', this.handleOutsideClick);
13442
+ window.addEventListener('resize', this.updatePosition);
13443
+ window.addEventListener('scroll', this.updatePosition, true);
13444
+ },
13445
+ beforeUnmount() {
13446
+ document.removeEventListener('click', this.handleOutsideClick);
13447
+ window.removeEventListener('resize', this.updatePosition);
13448
+ window.removeEventListener('scroll', this.updatePosition, true);
13449
+ },
13450
+ methods: {
13451
+ show() {
13452
+ this.isOpen = true;
13453
+ this.tooltipStyle = { visibility: 'hidden' };
13454
+ this.$nextTick(this.updatePosition);
13455
+ },
13456
+ hide() {
13457
+ this.isOpen = false;
13458
+ this.isPinned = false;
13459
+ },
13460
+ hideUnlessPinned() {
13461
+ if (!this.isPinned) {
13462
+ this.isOpen = false;
13463
+ }
13464
+ },
13465
+ toggle() {
13466
+ if (this.isPinned) {
13467
+ this.hide();
13468
+ return;
13469
+ }
13470
+
13471
+ this.isPinned = true;
13472
+ this.show();
13473
+ },
13474
+ handleFocusOut(event) {
13475
+ if (!this.$el.contains(event.relatedTarget)) {
13476
+ this.hideUnlessPinned();
13477
+ }
13478
+ },
13479
+ handleOutsideClick(event) {
13480
+ if (!this.$el.contains(event.target)) {
13481
+ this.hide();
13482
+ }
13483
+ },
13484
+ updatePosition() {
13485
+ if (!this.isOpen || !this.$refs.trigger || !this.$refs.tooltip) {
13486
+ return;
13487
+ }
13488
+
13489
+ const gap = 8;
13490
+ const viewportPadding = 8;
13491
+ const triggerRect = this.$refs.trigger.getBoundingClientRect();
13492
+ const tooltipRect = this.$refs.tooltip.getBoundingClientRect();
13493
+ const fitsBelow = triggerRect.bottom + gap + tooltipRect.height <= window.innerHeight - viewportPadding;
13494
+ const top = fitsBelow ?
13495
+ triggerRect.bottom + gap :
13496
+ Math.max(viewportPadding, triggerRect.top - gap - tooltipRect.height);
13497
+ const centeredLeft = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;
13498
+ const maxLeft = Math.max(viewportPadding, window.innerWidth - tooltipRect.width - viewportPadding);
13499
+ const left = Math.min(Math.max(viewportPadding, centeredLeft), maxLeft);
13500
+
13501
+ this.placement = fitsBelow ? 'bottom' : 'top';
13502
+ this.tooltipStyle = {
13503
+ left: `${Math.round(left)}px`,
13504
+ top: `${Math.round(top)}px`
13505
+ };
13506
+ }
13507
+ }
13508
+ });
13509
+
13510
+
13178
13511
  /***/ },
13179
13512
 
13180
13513
  /***/ "./frontend/src/update-document/update-document.js"
@@ -13436,6 +13769,9 @@ const map = {
13436
13769
  "./list-subdocument/list-subdocument.css": "./frontend/src/list-subdocument/list-subdocument.css",
13437
13770
  "./list-subdocument/list-subdocument.html": "./frontend/src/list-subdocument/list-subdocument.html",
13438
13771
  "./list-subdocument/list-subdocument.js": "./frontend/src/list-subdocument/list-subdocument.js",
13772
+ "./mcp/mcp": "./frontend/src/mcp/mcp.js",
13773
+ "./mcp/mcp.html": "./frontend/src/mcp/mcp.html",
13774
+ "./mcp/mcp.js": "./frontend/src/mcp/mcp.js",
13439
13775
  "./modal/modal": "./frontend/src/modal/modal.js",
13440
13776
  "./modal/modal.css": "./frontend/src/modal/modal.css",
13441
13777
  "./modal/modal.html": "./frontend/src/modal/modal.html",
@@ -13469,6 +13805,9 @@ const map = {
13469
13805
  "./pro-upgrade-modal/pro-upgrade-modal.js": "./frontend/src/pro-upgrade-modal/pro-upgrade-modal.js",
13470
13806
  "./routes": "./frontend/src/routes.js",
13471
13807
  "./routes.js": "./frontend/src/routes.js",
13808
+ "./script/script": "./frontend/src/script/script.js",
13809
+ "./script/script.html": "./frontend/src/script/script.html",
13810
+ "./script/script.js": "./frontend/src/script/script.js",
13472
13811
  "./splash/splash": "./frontend/src/splash/splash.js",
13473
13812
  "./splash/splash.html": "./frontend/src/splash/splash.html",
13474
13813
  "./splash/splash.js": "./frontend/src/splash/splash.js",
@@ -13491,6 +13830,9 @@ const map = {
13491
13830
  "./team/team": "./frontend/src/team/team.js",
13492
13831
  "./team/team.html": "./frontend/src/team/team.html",
13493
13832
  "./team/team.js": "./frontend/src/team/team.js",
13833
+ "./tooltip/tooltip": "./frontend/src/tooltip/tooltip.js",
13834
+ "./tooltip/tooltip.html": "./frontend/src/tooltip/tooltip.html",
13835
+ "./tooltip/tooltip.js": "./frontend/src/tooltip/tooltip.js",
13494
13836
  "./update-document/update-document": "./frontend/src/update-document/update-document.js",
13495
13837
  "./update-document/update-document.css": "./frontend/src/update-document/update-document.css",
13496
13838
  "./update-document/update-document.html": "./frontend/src/update-document/update-document.html",
@@ -13581,7 +13923,7 @@ __webpack_require__.r(__webpack_exports__);
13581
13923
  /* harmony export */ });
13582
13924
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
13583
13925
  /**
13584
- * @vue/reactivity v3.5.41
13926
+ * @vue/reactivity v3.5.43
13585
13927
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
13586
13928
  * @license MIT
13587
13929
  **/
@@ -14356,7 +14698,9 @@ function reactiveReadArray(array) {
14356
14698
  const raw = toRaw(array);
14357
14699
  if (raw === array) return raw;
14358
14700
  track(raw, "iterate", ARRAY_ITERATE_KEY);
14359
- return isShallow(array) ? raw : raw.map(toReactive);
14701
+ if (isShallow(array)) return raw;
14702
+ if (!isReadonly(array)) return raw.map(toReactive);
14703
+ return isReactive(array) ? raw.map((item) => toReadonly(toReactive(item))) : raw.map(toReadonly);
14360
14704
  }
14361
14705
  function shallowReadArray(arr) {
14362
14706
  track(arr = toRaw(arr), "iterate", ARRAY_ITERATE_KEY);
@@ -15754,7 +16098,7 @@ __webpack_require__.r(__webpack_exports__);
15754
16098
  /* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
15755
16099
  /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
15756
16100
  /**
15757
- * @vue/runtime-core v3.5.41
16101
+ * @vue/runtime-core v3.5.43
15758
16102
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
15759
16103
  * @license MIT
15760
16104
  **/
@@ -17822,13 +18166,15 @@ function createHydrationFunctions(rendererInternals) {
17822
18166
  getContainerType(container),
17823
18167
  optimized
17824
18168
  );
17825
- if (isAsyncWrapper(vnode) && !vnode.type.__asyncResolved) {
18169
+ if ((isAsyncWrapper(vnode) || vnode.component.asyncDep) && !vnode.component.subTree) {
17826
18170
  let subTree;
17827
18171
  if (isFragmentStart) {
17828
- subTree = createVNode(Fragment);
18172
+ subTree = createVNode(Static);
17829
18173
  subTree.anchor = nextNode ? nextNode.previousSibling : container.lastChild;
17830
18174
  } else {
17831
- subTree = node.nodeType === 3 ? createTextVNode("") : createVNode("div");
18175
+ subTree = node.nodeType === 3 ? createTextVNode("") : createVNode(
18176
+ node.nodeType === 8 ? Comment : "div"
18177
+ );
17832
18178
  }
17833
18179
  subTree.el = node;
17834
18180
  vnode.component.subTree = subTree;
@@ -18736,7 +19082,10 @@ const KeepAliveImpl = {
18736
19082
  if (pendingCacheKey != null) {
18737
19083
  if (isSuspense(instance.subTree.type)) {
18738
19084
  queuePostRenderEffect(() => {
18739
- cache.set(pendingCacheKey, getInnerChild(instance.subTree));
19085
+ const vnode = getInnerChild(instance.subTree);
19086
+ if (vnode.component) {
19087
+ cache.set(pendingCacheKey, vnode);
19088
+ }
18740
19089
  }, instance.subTree.suspense);
18741
19090
  } else {
18742
19091
  cache.set(pendingCacheKey, getInnerChild(instance.subTree));
@@ -19137,12 +19486,41 @@ const getPublicInstance = (i) => {
19137
19486
  if (isStatefulComponent(i)) return getComponentPublicInstance(i);
19138
19487
  return getPublicInstance(i.parent);
19139
19488
  };
19489
+ const resolveDevRootEl = (vnode) => {
19490
+ let found = false;
19491
+ while (true) {
19492
+ if (vnode.patchFlag > 0 && vnode.patchFlag & 2048) {
19493
+ const root = filterSingleRoot(vnode.children);
19494
+ if (!root) {
19495
+ return;
19496
+ }
19497
+ vnode = root;
19498
+ found = true;
19499
+ continue;
19500
+ }
19501
+ const component = vnode.component;
19502
+ if (component && component.subTree) {
19503
+ vnode = component.subTree;
19504
+ continue;
19505
+ }
19506
+ const suspense = vnode.suspense;
19507
+ if (suspense && suspense.activeBranch) {
19508
+ vnode = suspense.activeBranch;
19509
+ continue;
19510
+ }
19511
+ return found ? vnode.el : void 0;
19512
+ }
19513
+ };
19514
+ const getDevRootFragmentEl = (i) => {
19515
+ const el = i.subTree && resolveDevRootEl(i.subTree);
19516
+ return el === void 0 ? i.vnode.el : el;
19517
+ };
19140
19518
  const publicPropertiesMap = (
19141
19519
  // Move PURE marker to new line to workaround compiler discarding it
19142
19520
  // due to type annotation
19143
19521
  /* @__PURE__ */ (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.extend)(/* @__PURE__ */ Object.create(null), {
19144
19522
  $: (i) => i,
19145
- $el: (i) => i.vnode.el,
19523
+ $el: (i) => true ? getDevRootFragmentEl(i) : 0,
19146
19524
  $data: (i) => i.data,
19147
19525
  $props: (i) => true ? (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.shallowReadonly)(i.props) : 0,
19148
19526
  $attrs: (i) => true ? (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.shallowReadonly)(i.attrs) : 0,
@@ -20248,7 +20626,7 @@ function emit(instance, event, ...rawArgs) {
20248
20626
  args = rawArgs.map((a) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isString)(a) ? a.trim() : a);
20249
20627
  }
20250
20628
  if (modifiers.number) {
20251
- args = rawArgs.map(_vue_shared__WEBPACK_IMPORTED_MODULE_1__.looseToNumber);
20629
+ args = args.map(_vue_shared__WEBPACK_IMPORTED_MODULE_1__.looseToNumber);
20252
20630
  }
20253
20631
  }
20254
20632
  if (true) {
@@ -21274,6 +21652,12 @@ function baseCreateRenderer(options, createHydrationFns) {
21274
21652
  optimized = false;
21275
21653
  n2.dynamicChildren = null;
21276
21654
  }
21655
+ if (n2.dynamicChildren && n1 && n1.dynamicChildren && n1.dynamicChildren.hasOnce) {
21656
+ if (n2.dynamicChildren === _vue_shared__WEBPACK_IMPORTED_MODULE_1__.EMPTY_ARR) {
21657
+ n2.dynamicChildren = [];
21658
+ }
21659
+ n2.dynamicChildren.hasOnce = true;
21660
+ }
21277
21661
  const { type, ref, shapeFlag } = n2;
21278
21662
  switch (type) {
21279
21663
  case Text:
@@ -21880,6 +22264,7 @@ function baseCreateRenderer(options, createHydrationFns) {
21880
22264
  if (true) {
21881
22265
  pushWarningContext(n2);
21882
22266
  }
22267
+ n2.el = n1.el;
21883
22268
  updateComponentPreRender(instance, n2, optimized);
21884
22269
  if (true) {
21885
22270
  popWarningContext();
@@ -22471,7 +22856,7 @@ function baseCreateRenderer(options, createHydrationFns) {
22471
22856
  cacheIndex,
22472
22857
  memo
22473
22858
  } = vnode;
22474
- if (patchFlag === -2) {
22859
+ if (patchFlag === -2 || dynamicChildren && dynamicChildren.hasOnce) {
22475
22860
  optimized = false;
22476
22861
  }
22477
22862
  if (ref != null) {
@@ -22479,7 +22864,7 @@ function baseCreateRenderer(options, createHydrationFns) {
22479
22864
  setRef(ref, null, parentSuspense, vnode, true);
22480
22865
  (0,_vue_reactivity__WEBPACK_IMPORTED_MODULE_0__.resetTracking)();
22481
22866
  }
22482
- if (cacheIndex != null) {
22867
+ if (cacheIndex != null && (!vnode.ctx || vnode.ctx === parentComponent)) {
22483
22868
  parentComponent.renderCache[cacheIndex] = void 0;
22484
22869
  }
22485
22870
  if (shapeFlag & 256) {
@@ -22560,6 +22945,9 @@ function baseCreateRenderer(options, createHydrationFns) {
22560
22945
  }
22561
22946
  if (type === Static) {
22562
22947
  removeStaticNode(vnode);
22948
+ if (transition && !transition.persisted && transition.afterLeave) {
22949
+ transition.afterLeave();
22950
+ }
22563
22951
  return;
22564
22952
  }
22565
22953
  const performRemove = () => {
@@ -22603,6 +22991,9 @@ function baseCreateRenderer(options, createHydrationFns) {
22603
22991
  if (job) {
22604
22992
  job.flags |= 8;
22605
22993
  unmount(subTree, instance, parentSuspense, doRemove);
22994
+ } else if (instance.vnode.el && subTree) {
22995
+ subTree.transition = instance.vnode.transition;
22996
+ unmount(subTree, instance, parentSuspense, doRemove);
22606
22997
  }
22607
22998
  if (um) {
22608
22999
  queuePostRenderEffect(um, parentSuspense);
@@ -22817,7 +23208,7 @@ const SuspenseImpl = {
22817
23208
  rendererInternals
22818
23209
  );
22819
23210
  } else {
22820
- if (parentSuspense && parentSuspense.deps > 0 && !n1.suspense.isInFallback) {
23211
+ if (parentSuspense && parentSuspense.deps > 0 && !n1.suspense.isInFallback && !parentSuspense.isHydrating) {
22821
23212
  n2.suspense = n1.suspense;
22822
23213
  n2.suspense.vnode = n2;
22823
23214
  n2.el = n1.el;
@@ -22903,10 +23294,12 @@ function patchSuspense(n1, n2, container, anchor, parentComponent, namespace, sl
22903
23294
  if (pendingBranch) {
22904
23295
  suspense.pendingBranch = newBranch;
22905
23296
  if (isSameVNodeType(pendingBranch, newBranch)) {
23297
+ suspense.deps++;
22906
23298
  patch(
22907
23299
  pendingBranch,
22908
23300
  newBranch,
22909
- suspense.hiddenContainer,
23301
+ // a hydrating pending branch is adopted SSR DOM, already in place
23302
+ isHydrating ? container : suspense.hiddenContainer,
22910
23303
  null,
22911
23304
  parentComponent,
22912
23305
  suspense,
@@ -22914,10 +23307,11 @@ function patchSuspense(n1, n2, container, anchor, parentComponent, namespace, sl
22914
23307
  slotScopeIds,
22915
23308
  optimized
22916
23309
  );
23310
+ suspense.deps--;
22917
23311
  if (suspense.deps <= 0) {
22918
23312
  suspense.resolve();
22919
23313
  } else if (isInFallback) {
22920
- if (!isHydrating) {
23314
+ if (!isHydrating && !suspense.isFallbackMountPending) {
22921
23315
  patch(
22922
23316
  activeBranch,
22923
23317
  newFallback,
@@ -22958,7 +23352,7 @@ function patchSuspense(n1, n2, container, anchor, parentComponent, namespace, sl
22958
23352
  );
22959
23353
  if (suspense.deps <= 0) {
22960
23354
  suspense.resolve();
22961
- } else {
23355
+ } else if (!suspense.isFallbackMountPending) {
22962
23356
  patch(
22963
23357
  activeBranch,
22964
23358
  newFallback,
@@ -23179,6 +23573,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
23179
23573
  suspense.effects = [];
23180
23574
  if (isSuspensible) {
23181
23575
  if (parentSuspense && parentSuspense.pendingBranch && parentSuspenseId === parentSuspense.pendingId) {
23576
+ parentSuspenseId = void 0;
23182
23577
  parentSuspense.deps--;
23183
23578
  if (parentSuspense.deps === 0 && !sync) {
23184
23579
  parentSuspense.resolve();
@@ -23199,9 +23594,10 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
23199
23594
  if (!suspense.isInFallback) {
23200
23595
  return;
23201
23596
  }
23597
+ const latestFallback = suspense.vnode.ssFallback;
23202
23598
  patch(
23203
23599
  null,
23204
- fallbackVNode,
23600
+ latestFallback,
23205
23601
  container2,
23206
23602
  anchor2,
23207
23603
  parentComponent2,
@@ -23211,7 +23607,7 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
23211
23607
  slotScopeIds,
23212
23608
  optimized
23213
23609
  );
23214
- setActiveBranch(suspense, fallbackVNode);
23610
+ setActiveBranch(suspense, latestFallback);
23215
23611
  };
23216
23612
  const delayEnter = fallbackVNode.transition && fallbackVNode.transition.mode === "out-in";
23217
23613
  if (delayEnter) {
@@ -23251,6 +23647,12 @@ function createSuspenseBoundary(vnode, parentSuspense, parentComponent, containe
23251
23647
  return;
23252
23648
  }
23253
23649
  unsetCurrentInstance();
23650
+ if (hydratedEl && !instance.scope.active) {
23651
+ if (isInPendingSuspense && --suspense.deps === 0) {
23652
+ suspense.resolve();
23653
+ }
23654
+ return;
23655
+ }
23254
23656
  instance.asyncResolved = true;
23255
23657
  const { vnode: vnode2 } = instance;
23256
23658
  if (true) {
@@ -23672,7 +24074,8 @@ function cloneVNode(vnode, extraProps, mergeRef = false, cloneTransition = false
23672
24074
  el: vnode.el,
23673
24075
  anchor: vnode.anchor,
23674
24076
  ctx: vnode.ctx,
23675
- ce: vnode.ce
24077
+ ce: vnode.ce,
24078
+ cacheIndex: vnode.cacheIndex
23676
24079
  };
23677
24080
  if (transition && cloneTransition) {
23678
24081
  setTransitionHooks(
@@ -24479,7 +24882,7 @@ function isMemoSame(cached, memo) {
24479
24882
  return true;
24480
24883
  }
24481
24884
 
24482
- const version = "3.5.41";
24885
+ const version = "3.5.43";
24483
24886
  const warn = true ? warn$1 : 0;
24484
24887
  const ErrorTypeStrings = ErrorTypeStrings$1 ;
24485
24888
  const devtools = true ? devtools$1 : 0;
@@ -24690,7 +25093,7 @@ __webpack_require__.r(__webpack_exports__);
24690
25093
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
24691
25094
  /* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
24692
25095
  /**
24693
- * @vue/runtime-dom v3.5.41
25096
+ * @vue/runtime-dom v3.5.43
24694
25097
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
24695
25098
  * @license MIT
24696
25099
  **/
@@ -25273,7 +25676,11 @@ function setStyle(style, name, val) {
25273
25676
  }
25274
25677
  }
25275
25678
  if (name.startsWith("--")) {
25276
- style.setProperty(name, val);
25679
+ if (importantRE.test(val)) {
25680
+ style.setProperty(name, val.replace(importantRE, ""), "important");
25681
+ } else {
25682
+ style.setProperty(name, val);
25683
+ }
25277
25684
  } else {
25278
25685
  const prefixed = autoPrefix(style, name);
25279
25686
  if (importantRE.test(val)) {
@@ -26407,13 +26814,21 @@ const vModelSelect = {
26407
26814
  const selectedVal = Array.prototype.filter.call(el.options, (o) => o.selected).map(
26408
26815
  (o) => number ? (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.looseToNumber)(getValue(o)) : getValue(o)
26409
26816
  );
26410
- el[assignKey](
26411
- el.multiple ? (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isSet)(el._modelValue) ? new Set(selectedVal) : selectedVal : selectedVal[0]
26412
- );
26413
- el._assigning = true;
26414
- (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.nextTick)(() => {
26415
- el._assigning = false;
26416
- });
26817
+ const multiple = el.multiple;
26818
+ const assignedValue = multiple ? (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isSet)(el._modelValue) ? new Set(selectedVal) : selectedVal : selectedVal[0];
26819
+ const pending = el._pendingValue = [
26820
+ multiple,
26821
+ multiple ? (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(assignedValue) ? selectedVal.slice() : selectedVal : assignedValue
26822
+ ];
26823
+ try {
26824
+ el[assignKey](assignedValue);
26825
+ } finally {
26826
+ ;(0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_0__.nextTick)(() => {
26827
+ if (el._pendingValue === pending) {
26828
+ el._pendingValue = void 0;
26829
+ }
26830
+ });
26831
+ }
26417
26832
  });
26418
26833
  el[assignKey] = getModelAssigner(vnode);
26419
26834
  },
@@ -26427,11 +26842,25 @@ const vModelSelect = {
26427
26842
  el[assignKey] = getModelAssigner(vnode);
26428
26843
  },
26429
26844
  updated(el, { value }) {
26430
- if (!el._assigning) {
26845
+ const pending = el._pendingValue;
26846
+ el._pendingValue = void 0;
26847
+ if (!pending || pending[0] !== el.multiple || !isSameSelectValue(value, pending[1], pending[0])) {
26431
26848
  setSelected(el, value);
26432
26849
  }
26433
26850
  }
26434
26851
  };
26852
+ function isSameSelectValue(value, assignedValue, multiple) {
26853
+ if (!multiple) return (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.looseEqual)(value, assignedValue);
26854
+ if ((0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(value)) return (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.looseEqual)(value, assignedValue);
26855
+ if ((0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isSet)(value)) {
26856
+ if (value.size !== assignedValue.length) return false;
26857
+ for (const item of assignedValue) {
26858
+ if (!value.has(item)) return false;
26859
+ }
26860
+ return true;
26861
+ }
26862
+ return false;
26863
+ }
26435
26864
  function setSelected(el, value) {
26436
26865
  const isMultiple = el.multiple;
26437
26866
  const isArrayValue = (0,_vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__.isArray)(value);
@@ -26812,7 +27241,7 @@ __webpack_require__.r(__webpack_exports__);
26812
27241
  /* harmony export */ toTypeString: () => (/* binding */ toTypeString)
26813
27242
  /* harmony export */ });
26814
27243
  /**
26815
- * @vue/shared v3.5.41
27244
+ * @vue/shared v3.5.43
26816
27245
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
26817
27246
  * @license MIT
26818
27247
  **/
@@ -27080,10 +27509,10 @@ function normalizeStyle(value) {
27080
27509
  }
27081
27510
  const listDelimiterRE = /;(?![^(]*\))/g;
27082
27511
  const propertyDelimiterRE = /:([^]+)/;
27083
- const styleCommentRE = /\/\*[^]*?\*\//g;
27512
+ const styleCommentRE = /"(?:[^"\\]|\\[^])*"|'(?:[^'\\]|\\[^])*'|\\[^]|\/\*[^]*?\*\//g;
27084
27513
  function parseStringStyle(cssText) {
27085
27514
  const ret = {};
27086
- cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => {
27515
+ cssText.replace(styleCommentRE, (match) => match.startsWith("/*") ? "" : match).split(listDelimiterRE).forEach((item) => {
27087
27516
  if (item) {
27088
27517
  const tmp = item.split(propertyDelimiterRE);
27089
27518
  tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());
@@ -27153,7 +27582,7 @@ const isBooleanAttr = /* @__PURE__ */ makeMap(
27153
27582
  function includeBooleanAttr(value) {
27154
27583
  return !!value || value === "";
27155
27584
  }
27156
- const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/;
27585
+ const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u000d\u0020]/;
27157
27586
  const attrValidationCache = {};
27158
27587
  function isSSRSafeAttrName(name) {
27159
27588
  if (attrValidationCache.hasOwnProperty(name)) {
@@ -27244,15 +27673,72 @@ function getEscapedCssVarName(key, doubleEscape) {
27244
27673
  );
27245
27674
  }
27246
27675
 
27247
- function looseCompareArrays(a, b) {
27676
+ function looseCompareArrays(a, b, seen) {
27248
27677
  if (a.length !== b.length) return false;
27249
27678
  let equal = true;
27250
27679
  for (let i = 0; equal && i < a.length; i++) {
27251
- equal = looseEqual(a[i], b[i]);
27680
+ equal = looseEqual(a[i], b[i], seen);
27252
27681
  }
27253
27682
  return equal;
27254
27683
  }
27255
- function looseEqual(a, b) {
27684
+ function looseCompareCollections(a, b, seen) {
27685
+ if (a.size !== b.size) return false;
27686
+ const candidates = Array.from(b);
27687
+ const matched = new Uint8Array(candidates.length);
27688
+ for (const item of a) {
27689
+ let index = -1;
27690
+ for (let i = 0; i < candidates.length; i++) {
27691
+ if (!matched[i] && looseEqual(item, candidates[i], seen)) {
27692
+ index = i;
27693
+ break;
27694
+ }
27695
+ }
27696
+ if (index < 0) return false;
27697
+ matched[index] = 1;
27698
+ }
27699
+ return true;
27700
+ }
27701
+ function looseCompareObjects(a, b, seen) {
27702
+ let aValidType = isMap(a);
27703
+ let bValidType = isMap(b);
27704
+ if (aValidType || bValidType) {
27705
+ return aValidType && bValidType ? looseCompareCollections(a, b, seen) : false;
27706
+ }
27707
+ aValidType = isSet(a);
27708
+ bValidType = isSet(b);
27709
+ if (aValidType || bValidType) {
27710
+ return aValidType && bValidType ? looseCompareCollections(a, b, seen) : false;
27711
+ }
27712
+ const aKeysCount = Object.keys(a).length;
27713
+ const bKeysCount = Object.keys(b).length;
27714
+ if (aKeysCount !== bKeysCount) {
27715
+ return false;
27716
+ }
27717
+ for (const key in a) {
27718
+ const aHasKey = a.hasOwnProperty(key);
27719
+ const bHasKey = b.hasOwnProperty(key);
27720
+ if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key], seen)) {
27721
+ return false;
27722
+ }
27723
+ }
27724
+ return String(a) === String(b);
27725
+ }
27726
+ function looseCompareNested(a, b, seen, compare) {
27727
+ if (!seen) {
27728
+ seen = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
27729
+ }
27730
+ const [seenA, seenB] = seen;
27731
+ if (seenA.has(a) || seenB.has(b)) {
27732
+ return seenA.get(a) === b && seenB.get(b) === a;
27733
+ }
27734
+ seenA.set(a, b);
27735
+ seenB.set(b, a);
27736
+ const equal = compare(a, b, seen);
27737
+ seenA.delete(a);
27738
+ seenB.delete(b);
27739
+ return equal;
27740
+ }
27741
+ function looseEqual(a, b, seen) {
27256
27742
  if (a === b) return true;
27257
27743
  let aValidType = isDate(a);
27258
27744
  let bValidType = isDate(b);
@@ -27267,7 +27753,7 @@ function looseEqual(a, b) {
27267
27753
  aValidType = isArray(a);
27268
27754
  bValidType = isArray(b);
27269
27755
  if (aValidType || bValidType) {
27270
- return aValidType && bValidType ? looseCompareArrays(a, b) : false;
27756
+ return aValidType && bValidType ? looseCompareNested(a, b, seen, looseCompareArrays) : false;
27271
27757
  }
27272
27758
  aValidType = isObject(a);
27273
27759
  bValidType = isObject(b);
@@ -27275,18 +27761,7 @@ function looseEqual(a, b) {
27275
27761
  if (!aValidType || !bValidType) {
27276
27762
  return false;
27277
27763
  }
27278
- const aKeysCount = Object.keys(a).length;
27279
- const bKeysCount = Object.keys(b).length;
27280
- if (aKeysCount !== bKeysCount) {
27281
- return false;
27282
- }
27283
- for (const key in a) {
27284
- const aHasKey = a.hasOwnProperty(key);
27285
- const bHasKey = b.hasOwnProperty(key);
27286
- if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) {
27287
- return false;
27288
- }
27289
- }
27764
+ return looseCompareNested(a, b, seen, looseCompareObjects);
27290
27765
  }
27291
27766
  return String(a) === String(b);
27292
27767
  }
@@ -51775,6 +52250,128 @@ function parseUtf8ValidationOption(options) {
51775
52250
 
51776
52251
  /***/ },
51777
52252
 
52253
+ /***/ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js"
52254
+ /*!****************************************************************!*\
52255
+ !*** ./node_modules/mongoose/node_modules/mongodb/lib/bson.js ***!
52256
+ \****************************************************************/
52257
+ (__unused_webpack_module, exports, __webpack_require__) {
52258
+
52259
+ "use strict";
52260
+
52261
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
52262
+ exports.setUint32LE = exports.readUint8 = exports.readInt32LE = exports.UUID = exports.Timestamp = exports.serialize = exports.ObjectId = exports.NumberUtils = exports.MinKey = exports.MaxKey = exports.Long = exports.Int32 = exports.EJSON = exports.Double = exports.deserialize = exports.Decimal128 = exports.DBRef = exports.Code = exports.calculateObjectSize = exports.ByteUtils = exports.BSONType = exports.BSONSymbol = exports.BSONRegExp = exports.BSONError = exports.BSON = exports.Binary = void 0;
52263
+ exports.parseToElementsToArray = parseToElementsToArray;
52264
+ exports.pluckBSONSerializeOptions = pluckBSONSerializeOptions;
52265
+ exports.resolveBSONOptions = resolveBSONOptions;
52266
+ exports.parseUtf8ValidationOption = parseUtf8ValidationOption;
52267
+ /* eslint-disable no-restricted-imports */
52268
+ const bson_1 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
52269
+ var bson_2 = __webpack_require__(/*! bson */ "./node_modules/bson/lib/bson.mjs");
52270
+ Object.defineProperty(exports, "Binary", ({ enumerable: true, get: function () { return bson_2.Binary; } }));
52271
+ Object.defineProperty(exports, "BSON", ({ enumerable: true, get: function () { return bson_2.BSON; } }));
52272
+ Object.defineProperty(exports, "BSONError", ({ enumerable: true, get: function () { return bson_2.BSONError; } }));
52273
+ Object.defineProperty(exports, "BSONRegExp", ({ enumerable: true, get: function () { return bson_2.BSONRegExp; } }));
52274
+ Object.defineProperty(exports, "BSONSymbol", ({ enumerable: true, get: function () { return bson_2.BSONSymbol; } }));
52275
+ Object.defineProperty(exports, "BSONType", ({ enumerable: true, get: function () { return bson_2.BSONType; } }));
52276
+ Object.defineProperty(exports, "ByteUtils", ({ enumerable: true, get: function () { return bson_2.ByteUtils; } }));
52277
+ Object.defineProperty(exports, "calculateObjectSize", ({ enumerable: true, get: function () { return bson_2.calculateObjectSize; } }));
52278
+ Object.defineProperty(exports, "Code", ({ enumerable: true, get: function () { return bson_2.Code; } }));
52279
+ Object.defineProperty(exports, "DBRef", ({ enumerable: true, get: function () { return bson_2.DBRef; } }));
52280
+ Object.defineProperty(exports, "Decimal128", ({ enumerable: true, get: function () { return bson_2.Decimal128; } }));
52281
+ Object.defineProperty(exports, "deserialize", ({ enumerable: true, get: function () { return bson_2.deserialize; } }));
52282
+ Object.defineProperty(exports, "Double", ({ enumerable: true, get: function () { return bson_2.Double; } }));
52283
+ Object.defineProperty(exports, "EJSON", ({ enumerable: true, get: function () { return bson_2.EJSON; } }));
52284
+ Object.defineProperty(exports, "Int32", ({ enumerable: true, get: function () { return bson_2.Int32; } }));
52285
+ Object.defineProperty(exports, "Long", ({ enumerable: true, get: function () { return bson_2.Long; } }));
52286
+ Object.defineProperty(exports, "MaxKey", ({ enumerable: true, get: function () { return bson_2.MaxKey; } }));
52287
+ Object.defineProperty(exports, "MinKey", ({ enumerable: true, get: function () { return bson_2.MinKey; } }));
52288
+ Object.defineProperty(exports, "NumberUtils", ({ enumerable: true, get: function () { return bson_2.NumberUtils; } }));
52289
+ Object.defineProperty(exports, "ObjectId", ({ enumerable: true, get: function () { return bson_2.ObjectId; } }));
52290
+ Object.defineProperty(exports, "serialize", ({ enumerable: true, get: function () { return bson_2.serialize; } }));
52291
+ Object.defineProperty(exports, "Timestamp", ({ enumerable: true, get: function () { return bson_2.Timestamp; } }));
52292
+ Object.defineProperty(exports, "UUID", ({ enumerable: true, get: function () { return bson_2.UUID; } }));
52293
+ function parseToElementsToArray(bytes, offset) {
52294
+ const res = bson_1.BSON.onDemand.parseToElements(bytes, offset);
52295
+ return Array.isArray(res) ? res : [...res];
52296
+ }
52297
+ // validates buffer inputs, used for read operations
52298
+ const validateBufferInputs = (buffer, offset, length) => {
52299
+ if (offset < 0 || offset + length > buffer.length) {
52300
+ throw new RangeError(`Attempt to access memory outside buffer bounds: buffer length: ${buffer.length}, offset: ${offset}, length: ${length}`);
52301
+ }
52302
+ };
52303
+ // readInt32LE, reads a 32-bit integer from buffer at given offset
52304
+ // throws if offset is out of bounds
52305
+ const readInt32LE = (buffer, offset) => {
52306
+ validateBufferInputs(buffer, offset, 4);
52307
+ return bson_1.NumberUtils.getInt32LE(buffer, offset);
52308
+ };
52309
+ exports.readInt32LE = readInt32LE;
52310
+ // readUint8, reads a single unsigned byte from buffer at given offset
52311
+ const readUint8 = (buffer, offset) => {
52312
+ validateBufferInputs(buffer, offset, 1);
52313
+ return buffer[offset];
52314
+ };
52315
+ exports.readUint8 = readUint8;
52316
+ const setUint32LE = (destination, offset, value) => {
52317
+ destination[offset] = value;
52318
+ value >>>= 8;
52319
+ destination[offset + 1] = value;
52320
+ value >>>= 8;
52321
+ destination[offset + 2] = value;
52322
+ value >>>= 8;
52323
+ destination[offset + 3] = value;
52324
+ return 4;
52325
+ };
52326
+ exports.setUint32LE = setUint32LE;
52327
+ function pluckBSONSerializeOptions(options) {
52328
+ const { fieldsAsRaw, useBigInt64, promoteValues, promoteBuffers, promoteLongs, serializeFunctions, ignoreUndefined, bsonRegExp, raw, enableUtf8Validation } = options;
52329
+ return {
52330
+ fieldsAsRaw,
52331
+ useBigInt64,
52332
+ promoteValues,
52333
+ promoteBuffers,
52334
+ promoteLongs,
52335
+ serializeFunctions,
52336
+ ignoreUndefined,
52337
+ bsonRegExp,
52338
+ raw,
52339
+ enableUtf8Validation
52340
+ };
52341
+ }
52342
+ /**
52343
+ * Merge the given BSONSerializeOptions, preferring options over the parent's options, and
52344
+ * substituting defaults for values not set.
52345
+ *
52346
+ * @internal
52347
+ */
52348
+ function resolveBSONOptions(options, parent) {
52349
+ const parentOptions = parent?.bsonOptions;
52350
+ return {
52351
+ raw: options?.raw ?? parentOptions?.raw ?? false,
52352
+ useBigInt64: options?.useBigInt64 ?? parentOptions?.useBigInt64 ?? false,
52353
+ promoteLongs: options?.promoteLongs ?? parentOptions?.promoteLongs ?? true,
52354
+ promoteValues: options?.promoteValues ?? parentOptions?.promoteValues ?? true,
52355
+ promoteBuffers: options?.promoteBuffers ?? parentOptions?.promoteBuffers ?? false,
52356
+ ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
52357
+ bsonRegExp: options?.bsonRegExp ?? parentOptions?.bsonRegExp ?? false,
52358
+ serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
52359
+ fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {},
52360
+ enableUtf8Validation: options?.enableUtf8Validation ?? parentOptions?.enableUtf8Validation ?? true
52361
+ };
52362
+ }
52363
+ /** @internal */
52364
+ function parseUtf8ValidationOption(options) {
52365
+ const enableUtf8Validation = options?.enableUtf8Validation;
52366
+ if (enableUtf8Validation === false) {
52367
+ return { utf8: false };
52368
+ }
52369
+ return { utf8: { writeErrors: false } };
52370
+ }
52371
+ //# sourceMappingURL=bson.js.map
52372
+
52373
+ /***/ },
52374
+
51778
52375
  /***/ "./node_modules/mpath/index.js"
51779
52376
  /*!*************************************!*\
51780
52377
  !*** ./node_modules/mpath/index.js ***!
@@ -52532,7 +53129,7 @@ __webpack_require__.r(__webpack_exports__);
52532
53129
  /* 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");
52533
53130
  /* 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");
52534
53131
  /**
52535
- * vue v3.5.41
53132
+ * vue v3.5.43
52536
53133
  * (c) 2018-present Yuxi (Evan) You and Vue contributors
52537
53134
  * @license MIT
52538
53135
  **/
@@ -54317,6 +54914,17 @@ module.exports = "<div class=\"list-subdocument tooltip\">\n <div>{{shortenValu
54317
54914
 
54318
54915
  /***/ },
54319
54916
 
54917
+ /***/ "./frontend/src/mcp/mcp.html"
54918
+ /*!***********************************!*\
54919
+ !*** ./frontend/src/mcp/mcp.html ***!
54920
+ \***********************************/
54921
+ (module) {
54922
+
54923
+ "use strict";
54924
+ module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2\">\n <h1 class=\"text-xl font-bold text-content\">MCP</h1>\n <p class=\"mt-2 text-sm text-content-secondary\">\n Connect an MCP client such as Claude or ChatGPT to this Mongoose Studio workspace.\n </p>\n\n <div class=\"mt-6 rounded-lg border border-edge bg-surface p-4\">\n <label for=\"mcp-url\" class=\"block text-sm font-medium text-content\">MCP URL</label>\n <div class=\"mt-2 flex gap-2\">\n <input\n id=\"mcp-url\"\n type=\"text\"\n readonly\n :value=\"mcpUrl\"\n class=\"min-w-0 flex-1 rounded-md bg-muted px-3 py-2 font-mono text-sm text-content outline outline-1 -outline-offset-1 outline-edge-strong\"\n />\n <async-button\n type=\"button\"\n @click=\"copyMCPUrl\"\n class=\"rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover\">\n Copy\n </async-button>\n </div>\n </div>\n\n <div class=\"mt-8 grid gap-6 text-sm text-content-secondary\">\n <section>\n <h2 class=\"text-base font-semibold text-content\">Authentication and network access</h2>\n <p class=\"mt-2\">\n Without a Mongoose Studio API key, the MCP endpoint uses the deployment's\n <code class=\"rounded bg-muted px-1 py-0.5\">bindIp</code> restrictions. By default, this means an MCP client can only connect from localhost.\n </p>\n <p class=\"mt-2\">\n With an API key, MCP clients such as Claude and ChatGPT connect through OAuth. You will be prompted to sign in to Mongoose Studio and approve the connection before the client receives access.\n </p>\n </section>\n\n <section>\n <h2 class=\"text-base font-semibold text-content\">Permissions</h2>\n <p class=\"mt-2\">\n When an API key is configured, MCP access is scoped to the signed-in user's workspace permissions. During authorization, the user can explicitly grant fewer permissions, such as read-only access or a lower execution time limit. An MCP connection can never receive more access than that user already has.\n </p>\n </section>\n\n <section>\n <h2 class=\"text-base font-semibold text-content\">Supported operations</h2>\n <p class=\"mt-2\">\n The MCP server supports database CRUD operations, dashboards, and script creation. It does not execute standalone scripts directly.\n </p>\n <p class=\"mt-2\">\n An MCP client can create a script and return a Mongoose Studio URL. A person can then open that URL, review or edit the code, and choose whether to dry run or execute it.\n </p>\n </section>\n </div>\n</div>\n";
54925
+
54926
+ /***/ },
54927
+
54320
54928
  /***/ "./frontend/src/modal/modal.css"
54321
54929
  /*!**************************************!*\
54322
54930
  !*** ./frontend/src/modal/modal.css ***!
@@ -54390,7 +54998,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
54390
54998
  (module) {
54391
54999
 
54392
55000
  "use strict";
54393
- module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <aside class=\"bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <svg class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-gray-700 ring-inset ring-2 ring-gray-300 text-white hover:bg-gray-600': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 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 >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openAnalyzeSchemaModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Analyze Schema\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n :disabled=\"outputType !== 'table'\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm\"\n :class=\"outputType === 'table' ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n :disabled=\"searchText.trim().length === 0\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm\"\n :class=\"searchText.trim().length > 0 ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default Projection\n </async-button>\n <button\n @click=\"openDropCollectionModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50\"\n >\n Drop Collection\n </button>\n </div>\n </div>\n </div>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <projection-search\n ref=\"projectionSearch\"\n :value=\"projectionText\"\n :schema-paths=\"schemaPaths\"\n @projection-input=\"projectionText = $event\"\n @apply=\"applyProjectionFromInput\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n <span class=\"block mt-2\">\n Need help?\n <a\n href=\"https://discord.gg/P3YCfKYxpy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >Ask in Discord</a>\n or\n <a\n href=\"https://github.com/mongoosejs/studio/issues\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >\n open a GitHub issue.\n </a>\n </span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors\"\n :class=\"selectedDocuments.some(x => x._id.toString() === document._id.toString())\n ? 'bg-blue-200'\n : 'hover:bg-muted'\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"outputType === 'table' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary border-t border-edge bg-surface\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded'\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex min-w-0 flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative min-w-0 max-w-full transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <router-link\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :to=\"{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n >\n Open\n </router-link>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n <div v-if=\"outputType === 'json' && (loadingMore || (status === 'loading' && documents.length > 0))\" class=\"flex items-center justify-center gap-2 py-3 text-sm text-content-tertiary\">\n <img src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>Loading documents…</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">&times;</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowAnalyzeSchemaModal\" containerClass=\"md:!w-[70vw]\">\n <template v-slot:body>\n <analyze-schema-modal\n :current-model=\"currentModel\"\n @close=\"shouldShowAnalyzeSchemaModal = false\"\n ></analyze-schema-modal>\n </template>\n </modal>\n <modal v-if=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">&times;</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">&times;</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">&times;</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowDropCollectionModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDropCollectionModal = false\">&times;</div>\n <h2 class=\"text-xl font-bold mb-3\">Are you sure?</h2>\n <p class=\"text-content-secondary mb-3\">\n This will permanently drop the <span class=\"font-semibold\">{{ currentModel }}</span> collection and all its documents.\n </p>\n <div v-if=\"requiresDropCollectionNameConfirmation()\" class=\"rounded border border-red-300 bg-red-50 px-3 py-2 mb-4\">\n <p class=\"text-sm font-semibold text-red-700\">Danger zone</p>\n <p class=\"text-sm text-red-700 mb-2\">Type <span class=\"font-mono font-semibold\">{{ currentModel }}</span> to confirm.</p>\n <input\n v-model=\"dropCollectionConfirmName\"\n type=\"text\"\n :placeholder=\"currentModel\"\n class=\"w-full rounded border border-red-300 px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-red-200\"\n />\n </div>\n <div class=\"flex gap-4\">\n <async-button\n @click=\"dropCollection\"\n :disabled=\"requiresDropCollectionNameConfirmation() && dropCollectionConfirmName !== currentModel\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Drop Collection\n </async-button>\n <button @click=\"shouldShowDropCollectionModal = false\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
55001
+ module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <aside class=\"bg-page border-r overflow-hidden transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative shrink-0 flex flex-col top-[55px] bottom-0 lg:top-auto lg:bottom-auto lg:h-full\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <!-- Search -->\n <div class=\"p-3 shrink-0\">\n <div class=\"relative\">\n <svg class=\"absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n <input\n v-model=\"modelSearch\"\n type=\"text\"\n placeholder=\"Find model...\"\n @keydown.esc=\"modelSearch = ''\"\n class=\"w-full rounded-md border border-edge bg-surface py-1.5 pl-8 pr-3 text-sm text-content placeholder:text-gray-400 focus:border-edge-strong focus:outline-none focus:ring-1 focus:ring-gray-300\"\n />\n </div>\n </div>\n <!-- Model list (scrollable) -->\n <nav class=\"flex-1 overflow-y-auto px-2 pb-2\">\n <!-- Recently Viewed -->\n <div v-if=\"filteredRecentModels.length > 0 && !modelSearch.trim()\">\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">Recently Viewed</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredRecentModels\" :key=\"'recent-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div class=\"border-b border-edge my-2\"></div>\n </div>\n <!-- All Models / Search Results -->\n <div class=\"px-2 py-1 text-xs font-semibold text-gray-400 uppercase tracking-wider\">{{ modelSearch.trim() ? 'Search Results' : 'All Models' }}</div>\n <ul role=\"list\">\n <li v-for=\"model in filteredModels\" :key=\"'all-' + model\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"flex items-center rounded-md py-1.5 px-2 text-sm text-content-secondary\"\n :class=\"model === currentModel ? 'bg-gray-200 font-semibold text-content' : 'hover:bg-muted'\">\n <span class=\"truncate\" v-html=\"highlightMatch(model)\"></span>\n <span\n v-if=\"modelDocumentCounts && modelDocumentCounts[model] !== undefined && model !== currentModel\"\n class=\"ml-auto text-xs text-gray-400 bg-muted rounded px-1.5 py-[1px]\"\n >\n {{formatCompactCount(modelDocumentCounts[model])}}\n </span>\n </router-link>\n </li>\n </ul>\n <div v-if=\"filteredModels.length === 0 && modelSearch.trim()\" class=\"px-2 py-2 text-sm text-content-tertiary\">\n No models match \"{{modelSearch}}\"\n </div>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100 rounded-md\">\n No models found\n </div>\n </nav>\n <!-- Bottom toolbar -->\n <div class=\"shrink-0 border-t border-edge bg-page px-2 py-1.5 flex items-center gap-1\">\n <button\n type=\"button\"\n @click=\"hideSidebar = true\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Hide sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.75 4.5-7.5 7.5 7.5 7.5m-6-15L5.25 12l7.5 7.5\" />\n </svg>\n </button>\n <button\n type=\"button\"\n @click=\"openModelSwitcher\"\n class=\"rounded p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n title=\"Quick switch (Ctrl+P)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z\" />\n </svg>\n </button>\n </div>\n </aside>\n <div class=\"documents bg-slate-50 min-w-0\" ref=\"documentsList\">\n <div class=\"documents-menu bg-slate-50\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <button\n v-if=\"hideSidebar === true || hideSidebar === null\"\n type=\"button\"\n @click=\"hideSidebar = false\"\n class=\"shrink-0 rounded-md p-1.5 text-gray-400 hover:text-gray-600 hover:bg-muted\"\n :class=\"hideSidebar === null ? 'lg:hidden' : ''\"\n title=\"Show sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m5.25 4.5 7.5 7.5-7.5 7.5m6-15 7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <tooltip\n v-if=\"numDocumentsError\"\n :text=\"'Error counting documents: ' + numDocumentsError\"\n >\n <span\n class=\"inline-flex rounded-md bg-red-50 px-2 py-2 text-sm text-red-700 ring-1 ring-inset ring-red-200\"\n role=\"alert\"\n >\n Count Error\n </span>\n </tooltip>\n <span v-else-if=\"numDocuments == null\">Loading...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{\n 'bg-gray-700 ring-inset ring-2 ring-gray-300 text-white hover:bg-gray-600': selectMultiple,\n 'bg-primary hover:bg-primary-hover text-primary-text': !selectMultiple\n }\"\n class=\"rounded px-2 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 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 >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-surface px-2 py-2 text-sm font-semibold text-content-secondary shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-page focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-surface shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-50\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Create\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Indexes\n </button>\n <button\n @click=\"openAnalyzeSchemaModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Analyze Schema\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Collection Info\n </button>\n <button\n @click=\"toggleRowNumbers()\"\n :disabled=\"outputType !== 'table'\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm\"\n :class=\"outputType === 'table' ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'\"\n >\n {{ showRowNumbers ? 'Hide row numbers' : 'Show row numbers' }}\n </button>\n <button\n @click=\"resetFilter()\"\n :disabled=\"searchText.trim().length === 0\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm\"\n :class=\"searchText.trim().length > 0 ? 'text-content-secondary hover:bg-muted' : 'text-content-tertiary cursor-not-allowed opacity-50'\"\n >\n Reset Filter\n </button>\n <button\n @click=\"toggleProjectionMenu(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm hover:bg-muted\"\n :class=\"isProjectionMenuSelected ? 'text-primary font-semibold' : 'text-content-secondary'\"\n >\n {{ isProjectionMenuSelected ? 'Projection (On)' : 'Projection' }}\n </button>\n <button\n v-if=\"isProjectionMenuSelected\"\n @click=\"clearProjection()\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Reset Projection\n </button>\n <async-button\n v-if=\"isProjectionMenuSelected\"\n type=\"button\"\n @click=\"applyDefaultProjectionColumns()\"\n class=\"block w-full px-4 py-2 text-left text-sm text-content-secondary hover:bg-muted\"\n >\n Default Projection\n </async-button>\n <button\n @click=\"openDropCollectionModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-red-700 hover:bg-red-50\"\n >\n Drop Collection\n </button>\n </div>\n </div>\n </div>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-page focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-surface'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-muted' : 'text-gray-400 hover:bg-page',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-surface' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </svg>\n </button>\n </span>\n </div>\n <div v-if=\"isProjectionMenuSelected && (outputType === 'table' || outputType === 'json')\" class=\"flex items-center gap-2 w-full mt-2 flex-shrink-0\">\n <projection-search\n ref=\"projectionSearch\"\n :value=\"projectionText\"\n :schema-paths=\"schemaPaths\"\n @projection-input=\"projectionText = $event\"\n @apply=\"applyProjectionFromInput\"\n />\n </div>\n </div>\n <!-- In JSON view, this container is the scrollable element used for infinite scroll. -->\n <div class=\"documents-container relative\" ref=\"documentsContainerScroll\" @scroll=\"checkIfScrolledToBottom\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n <span class=\"block mt-2\">\n Need help?\n <a\n href=\"https://discord.gg/P3YCfKYxpy\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >Ask in Discord</a>\n or\n <a\n href=\"https://github.com/mongoosejs/studio/issues\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"underline font-medium text-red-800 hover:text-red-900\"\n >\n open a GitHub issue.\n </a>\n </span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'table'\" class=\"flex-1 min-h-0 flex flex-col overflow-hidden\">\n <div\n ref=\"documentsScrollContainer\"\n class=\"overflow-x-auto overflow-y-auto flex-1 min-h-0 border border-edge rounded-lg bg-surface\"\n @scroll=\"checkIfScrolledToBottom\"\n >\n <table class=\"min-w-full border-collapse text-sm\">\n <thead class=\"sticky top-0 z-10 bg-slate-100 dark:bg-shark-800 border-b border-edge\">\n <tr>\n <th v-if=\"showRowNumbers\" class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge whitespace-nowrap align-middle w-0\">\n #\n </th>\n <th\n v-for=\"path in tableDisplayPaths\"\n :key=\"path.path\"\n class=\"px-3 py-2.5 text-left font-medium text-content border-r border-edge last:border-r-0 whitespace-nowrap align-middle\"\n >\n <div class=\"flex items-center gap-2\">\n <span\n @click=\"addPathFilter(path.path)\"\n class=\"cursor-pointer hover:text-primary truncate min-w-0\"\n :title=\"path.path\"\n >\n {{ path.path }}\n </span>\n <span class=\"text-xs text-content-tertiary shrink-0\">({{ path.instance || 'unknown' }})</span>\n <span class=\"inline-flex shrink-0 gap-0.5 items-center\">\n <button\n type=\"button\"\n @click.stop=\"sortDocs(1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === 1 }\"\n :title=\"sortBy[path.path] === 1 ? 'Clear sort' : 'Sort ascending'\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n @click.stop=\"sortDocs(-1, path.path)\"\n class=\"p-0.5 rounded text-content-tertiary hover:text-content hover:bg-muted\"\n :class=\"{ 'text-primary font-semibold': sortBy[path.path] === -1 }\"\n :title=\"sortBy[path.path] === -1 ? 'Clear sort' : 'Sort descending'\"\n >\n ↓\n </button>\n <button\n v-if=\"filteredPaths.length > 0\"\n type=\"button\"\n @click.stop=\"removeField(path)\"\n class=\"p-1.5 rounded-md border border-transparent text-content-tertiary hover:text-valencia-600 hover:bg-valencia-50 hover:border-valencia-200 focus:outline-none focus:ring-2 focus:ring-valencia-500/30\"\n title=\"Remove column\"\n aria-label=\"Remove column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n </div>\n </th>\n <th v-if=\"filteredPaths.length > 0\" class=\"px-2 py-2.5 border-r border-edge last:border-r-0 align-middle w-0 bg-slate-50 dark:bg-shark-800/80\">\n <div class=\"relative\" ref=\"addFieldContainer\">\n <button\n type=\"button\"\n @click=\"toggleAddFieldDropdown()\"\n class=\"flex items-center justify-center w-8 h-8 rounded border border-dashed border-edge text-content-tertiary hover:border-primary hover:text-primary hover:bg-primary-subtle/30\"\n title=\"Add column\"\n aria-label=\"Add column\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\" class=\"w-5 h-5\"> <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" /> </svg>\n </button>\n <div\n v-if=\"showAddFieldDropdown\"\n class=\"absolute right-0 top-full mt-1 z-[100] min-w-[180px] max-w-[280px] rounded-md border border-edge bg-surface shadow-lg py-1 max-h-48 overflow-y-auto\"\n >\n <input\n v-if=\"availablePathsToAdd.length > 5\"\n ref=\"addFieldFilterInput\"\n v-model=\"addFieldFilterText\"\n type=\"text\"\n placeholder=\"Filter fields...\"\n class=\"mx-2 mb-1 w-[calc(100%-1rem)] rounded border border-edge px-2 py-1 text-sm\"\n @click.stop\n />\n <button\n v-for=\"p in filteredPathsToAdd\"\n :key=\"p.path\"\n type=\"button\"\n class=\"w-full px-3 py-1.5 text-left text-sm hover:bg-muted\"\n @click.stop=\"addField(p)\"\n >\n {{ p.path }}\n </button>\n <p v-if=\"filteredPathsToAdd.length === 0\" class=\"px-3 py-2 text-sm text-content-tertiary\">\n {{ addFieldFilterText.trim() ? 'No matching fields' : 'All fields added' }}\n </p>\n </div>\n </div>\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-surface\">\n <tr\n v-for=\"(document, docIndex) in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentClick(document, $event)\"\n class=\"border-b border-edge cursor-pointer transition-colors\"\n :class=\"selectedDocuments.some(x => x._id.toString() === document._id.toString())\n ? 'bg-blue-200'\n : 'hover:bg-muted'\"\n >\n <td v-if=\"showRowNumbers\" class=\"px-3 py-2 border-r border-edge align-top text-content-tertiary whitespace-nowrap\">\n {{ docIndex + 1 }}\n </td>\n <td\n v-for=\"schemaPath in tableDisplayPaths\"\n :key=\"schemaPath.path\"\n class=\"px-3 py-2 border-r border-edge last:border-r-0 align-top max-w-[280px]\"\n >\n <div class=\"table-cell-content flex items-center gap-1.5 min-w-0 group\">\n <span class=\"min-w-0 overflow-hidden text-ellipsis flex-1\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\"\n />\n </span>\n <button\n type=\"button\"\n class=\"table-cell-copy shrink-0 p-1 rounded text-content-tertiary hover:text-content hover:bg-muted focus:outline-none focus:ring-1 focus:ring-edge\"\n aria-label=\"Copy cell value\"\n @click.stop=\"copyCellValue(getValueForPath(document, schemaPath.path))\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8 5H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1M8 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M8 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m0 0h2a2 2 0 0 1 2 2v3m2 4H10m0 0l3-3m-3 3l3 3\" />\n </svg>\n </button>\n </div>\n </td>\n <td v-if=\"filteredPaths.length > 0\" class=\"w-0 px-0 py-0 border-r border-edge last:border-r-0 bg-slate-50/50 dark:bg-shark-800/30\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n <div\n v-if=\"outputType === 'table' && (documentsError || loadingMore || (status === 'loading' && documents.length > 0))\"\n class=\"flex items-center justify-center gap-2 py-3 text-sm border-t\"\n :class=\"documentsError ? 'border-red-200 bg-red-50 text-red-700' : 'border-edge bg-surface text-content-tertiary'\"\n :role=\"documentsError ? 'alert' : null\"\n >\n <img v-if=\"!documentsError\" src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>{{documentsError || 'Loading documents…'}}</span>\n </div>\n <p v-if=\"outputType === 'table' && documents.length === 0 && status === 'loaded' && !documentsError\" class=\"mt-2 text-sm text-content-tertiary px-1\">\n No documents to show. Use Projection in the menu to choose columns.\n </p>\n </div>\n <div v-else-if=\"outputType === 'json'\" class=\"flex min-w-0 flex-col space-y-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative min-w-0 max-w-full transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-surface'\n ]\"\n >\n <router-link\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-primary px-2 py-1 text-xs font-semibold text-primary-text shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :to=\"{ path: '/model/' + currentModel + '/document/' + document._id, query: $route.query }\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click.stop\n >\n Open\n </router-link>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n <div\n v-if=\"outputType === 'json' && (documentsError || loadingMore || (status === 'loading' && documents.length > 0))\"\n class=\"flex items-center justify-center gap-2 py-3 text-sm\"\n :class=\"documentsError ? 'rounded-md border border-red-200 bg-red-50 text-red-700' : 'text-content-tertiary'\"\n :role=\"documentsError ? 'alert' : null\"\n >\n <img v-if=\"!documentsError\" src=\"images/loader.gif\" alt=\"\" class=\"h-5 w-5\">\n <span>{{documentsError || 'Loading documents…'}}</span>\n </div>\n </div>\n <div v-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-surface border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-content-secondary\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-edge-strong py-1 px-2 text-sm focus:border-primary focus:ring-primary\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-primary-text shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-primary hover:bg-primary-hover'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></div>\n </div>\n <div v-if=\"status === 'loading' && !loadingMore && documents.length === 0\" class=\"loader loader-overlay\" aria-busy=\"true\">\n <img src=\"images/loader.gif\" alt=\"Loading\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">&times;</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-primary-subtle px-2 py-0.5 text-xs font-semibold text-primary\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowAnalyzeSchemaModal\" containerClass=\"md:!w-[70vw]\">\n <template v-slot:body>\n <analyze-schema-modal\n :current-model=\"currentModel\"\n @close=\"shouldShowAnalyzeSchemaModal = false\"\n ></analyze-schema-modal>\n </template>\n </modal>\n <modal v-if=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Documents</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Indexes</div>\n <div class=\"text-content\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Index Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Total Storage Size</div>\n <div class=\"text-content\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Collation</div>\n <div class=\"text-content\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-muted p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-content-secondary\">Capped</div>\n <div class=\"text-content\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">&times;</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">&times;</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">&times;</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowDropCollectionModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDropCollectionModal = false\">&times;</div>\n <h2 class=\"text-xl font-bold mb-3\">Are you sure?</h2>\n <p class=\"text-content-secondary mb-3\">\n This will permanently drop the <span class=\"font-semibold\">{{ currentModel }}</span> collection and all its documents.\n </p>\n <div v-if=\"requiresDropCollectionNameConfirmation()\" class=\"rounded border border-red-300 bg-red-50 px-3 py-2 mb-4\">\n <p class=\"text-sm font-semibold text-red-700\">Danger zone</p>\n <p class=\"text-sm text-red-700 mb-2\">Type <span class=\"font-mono font-semibold\">{{ currentModel }}</span> to confirm.</p>\n <input\n v-model=\"dropCollectionConfirmName\"\n type=\"text\"\n :placeholder=\"currentModel\"\n class=\"w-full rounded border border-red-300 px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-red-200\"\n />\n </div>\n <div class=\"flex gap-4\">\n <async-button\n @click=\"dropCollection\"\n :disabled=\"requiresDropCollectionNameConfirmation() && dropCollectionConfirmName !== currentModel\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Drop Collection\n </async-button>\n <button @click=\"shouldShowDropCollectionModal = false\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-page0\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n <model-switcher\n :show=\"showModelSwitcher\"\n :models=\"models\"\n :recently-viewed-models=\"recentlyViewedModels\"\n :model-document-counts=\"modelDocumentCounts\"\n @close=\"showModelSwitcher = false\"\n @select=\"selectSwitcherModel\"\n ></model-switcher>\n</div>\n";
54394
55002
 
54395
55003
  /***/ },
54396
55004
 
@@ -54423,7 +55031,7 @@ module.exports = ".active {\n text-decoration: underline;\n}\n\n.navbar .nav-le
54423
55031
  (module) {
54424
55032
 
54425
55033
  "use strict";
54426
- module.exports = "<div>\n<div class=\"navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4\">\n <!-- Left: Logo + Brand -->\n <div class=\"flex items-center gap-2 self-center\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-10 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content whitespace-nowrap\">Mongoose Studio</span>\n </router-link>\n </div>\n <!-- Center: Nav Links -->\n <nav class=\"flex items-stretch gap-6\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"dashboardView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && supportsAI\"\n href=\"#/chat\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !supportsAI\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a\n v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"taskView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">\n Tasks\n </a>\n <a\n href=\"https://studio.mongoosejs.io/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-content-tertiary hover:text-content\"\n >\n Docs\n </a>\n </nav>\n <!-- Right: Env Badge + User -->\n <div class=\"flex items-center justify-end gap-3 self-center\">\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-5 w-5\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <div\n v-if=\"!!state.nodeEnv\"\n title=\"NODE_ENV\"\n class=\"inline-flex items-center rounded px-2 py-1 text-xs text-content-tertiary bg-surface border border-edge gap-2\"\n >\n <span\n :class=\"warnEnv ? 'bg-red-400' : 'bg-yellow-400'\"\n class=\"inline-block rounded-full\"\n style=\"width: 0.5em; height: 0.5em;\"\n ></span>\n <span>{{state.nodeEnv}}</span>\n </div>\n <button\n v-if=\"!user || !hasAPIKey\"\n type=\"button\"\n @click=\"toggleDarkMode\"\n :title=\"darkMode ? 'Switch to light mode' : 'Switch to dark mode'\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/>\n </svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/>\n </svg>\n </button>\n <div class=\"flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-gray-300\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n\n <div\n v-if=\"showFlyout\"\n class=\"absolute right-0 top-[90%] w-48 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n style=\"z-index: 10000\"\n tabindex=\"-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode(); showFlyout = false\"\n class=\"w-full cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle text-left flex items-center gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n </div>\n</div>\n<!-- Mobile navbar -->\n<div class=\"navbar w-full bg-page flex justify-between border-b border-edge !h-[55px] md:hidden\">\n <div class=\"flex items-center gap-2 pl-4\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-8 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content\">Mongoose Studio</span>\n </router-link>\n </div>\n <div class=\"flex items-center gap-1 pr-2\">\n <button\n type=\"button\"\n class=\"rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" style=\"z-index: 100000\" class=\"fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-edge\">\n <router-link :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-[32px] text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <div class=\"flex items-center gap-2 px-3 py-2\" v-if=\"!user || !hasAPIKey\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"inline-flex items-center gap-2 rounded-md px-3 py-2 text-base font-medium text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n <span>{{ darkMode ? 'Light mode' : 'Dark mode' }}</span>\n </button>\n </div>\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && supportsAI\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !supportsAI\"\n class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"taskView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Tasks</a>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-muted text-content px-3 py-2 text-base font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-page rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-content font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"w-full inline-flex items-center gap-2 px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle text-left\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n\n<modal\n v-if=\"showRecentPagesModal\"\n flush-container\n containerClass=\"!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content\"\n bodyClass=\"modal-recent-pages-body !p-0\"\n>\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeRecentPagesModal\" role=\"button\" aria-label=\"Close recent pages\">&times;</div>\n <div\n class=\"shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900\"\n >\n <h2 class=\"m-0 text-lg font-semibold text-content\">Recent pages</h2>\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50\"\n @click=\"clearRecentPagesHistory\"\n :disabled=\"recentPagesList.length === 0\"\n >\n Clear\n </button>\n </div>\n <div class=\"modal-recent-pages-scroll\">\n <div\n v-if=\"recentPagesList.length === 0\"\n class=\"m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary\"\n >\n No recent pages yet.\n </div>\n <button\n v-for=\"entry in recentPagesList\"\n :key=\"entry.path + '-' + entry.visitedAt\"\n type=\"button\"\n class=\"w-full px-4 py-2.5 text-left hover:bg-muted\"\n :class=\"entry.path === $route.fullPath ? 'bg-primary-subtle' : ''\"\n @click=\"goToRecentPage(entry)\"\n >\n <div class=\"truncate text-sm font-medium text-content\">{{ entry.label }}</div>\n <div class=\"truncate font-mono text-xs text-content-tertiary\">{{ entry.path }}</div>\n </button>\n </div>\n </template>\n</modal>\n</div>\n";
55034
+ module.exports = "<div>\n<div class=\"navbar w-full bg-page border-b border-edge !h-[55px] hidden md:grid grid-cols-[1fr_auto_1fr] items-stretch px-4\">\n <!-- Left: Logo + Brand -->\n <div class=\"flex items-center gap-2 self-center\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-10 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content whitespace-nowrap\">Mongoose Studio</span>\n </router-link>\n </div>\n <!-- Center: Nav Links -->\n <nav class=\"flex items-stretch gap-6\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"dashboardView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && supportsAI\"\n href=\"#/chat\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"chatView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !supportsAI\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a\n v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"inline-flex items-center px-1 border-b-2 text-sm font-medium\"\n :class=\"taskView ? 'text-content border-primary' : 'border-transparent text-content-tertiary hover:text-content'\">\n Tasks\n </a>\n <a\n href=\"https://studio.mongoosejs.io/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium text-content-tertiary hover:text-content\"\n >\n Docs\n </a>\n </nav>\n <!-- Right: Env Badge + User -->\n <div class=\"flex items-center justify-end gap-3 self-center\">\n <button\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-5 w-5\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <div\n v-if=\"!!state.nodeEnv\"\n title=\"NODE_ENV\"\n class=\"inline-flex items-center rounded px-2 py-1 text-xs text-content-tertiary bg-surface border border-edge gap-2\"\n >\n <span\n :class=\"warnEnv ? 'bg-red-400' : 'bg-yellow-400'\"\n class=\"inline-block rounded-full\"\n style=\"width: 0.5em; height: 0.5em;\"\n ></span>\n <span>{{state.nodeEnv}}</span>\n </div>\n <button\n v-if=\"!user || !hasAPIKey\"\n type=\"button\"\n @click=\"toggleDarkMode\"\n :title=\"darkMode ? 'Switch to light mode' : 'Switch to dark mode'\"\n class=\"inline-flex items-center justify-center rounded-md p-2 text-content-tertiary hover:text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/>\n </svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/>\n </svg>\n </button>\n <div class=\"flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-primary px-2 py-2 text-sm font-semibold text-primary-text shadow-sm hover:bg-primary-hover focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-gray-300\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n\n <div\n v-if=\"showFlyout\"\n class=\"absolute right-0 top-[90%] w-48 origin-top-right rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\"\n role=\"menu\"\n aria-orientation=\"vertical\"\n aria-labelledby=\"user-menu-button\"\n style=\"z-index: 10000\"\n tabindex=\"-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode(); showFlyout = false\"\n class=\"w-full cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle text-left flex items-center gap-2\"\n role=\"menuitem\"\n tabindex=\"-1\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <router-link v-if=\"mcpEnabled\" to=\"/mcp\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\">MCP</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" title=\"MCP is disabled for this deployment\">\n MCP\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-content-secondary hover:bg-primary-subtle\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n </div>\n</div>\n<!-- Mobile navbar -->\n<div class=\"navbar w-full bg-page flex justify-between border-b border-edge !h-[55px] md:hidden\">\n <div class=\"flex items-center gap-2 pl-4\">\n <router-link class=\"flex items-center gap-2\" :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-8 text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n <span class=\"text-base font-semibold text-content\">Mongoose Studio</span>\n </router-link>\n </div>\n <div class=\"flex items-center gap-1 pr-2\">\n <button\n type=\"button\"\n class=\"rounded-md p-2 text-content-tertiary hover:bg-muted hover:text-content-secondary md:hidden\"\n title=\"Recent pages\"\n aria-label=\"Recent pages\"\n aria-haspopup=\"dialog\"\n :aria-expanded=\"showRecentPagesModal ? 'true' : 'false'\"\n @click=\"openRecentPagesModal\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"h-6 w-6\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </button>\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" style=\"z-index: 100000\" class=\"fixed inset-0 bg-page shadow-lg transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-edge\">\n <router-link :to=\"{ name: defaultRoute }\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-[32px] text-primary\" viewBox=\"0 0 250 250\" fill=\"currentColor\" aria-label=\"Mongoose Studio Logo\">\n <path d=\"M 241.66,125.01 C 237.81,112.98 227.41,111.95 216.76,110.36 C 215.12,95.83 210.99,83.09 202.29,70.33 C 202.99,68.37 201.92,66.65 200.74,65.11 C 183.14,41.46 157.52,27 124.66,27 C 92.62,27 66.09,40.77 48.28,63.38 C 46.21,65.85 46.26,67.71 47.12,69.36 C 37.81,81.15 33.21,93.78 30.99,109.86 C 20.19,110.58 11.71,115.92 9.04,124.94 C 3.28,145.66 5.62,157.95 10.01,169.16 C 16.14,184.65 29.12,186.31 36.04,185.81 C 42.48,185.68 44.94,184.31 44.72,182.11 C 44.26,177.79 41.01,165.31 41.01,149.12 C 41.01,138.22 43.19,127.74 46.21,117.7 C 47.51,113.33 44.66,112.05 39.16,110.28 C 41.01,96.88 45.88,84.85 52.61,73.79 C 55.17,75.56 58.07,74.88 59.61,72.71 C 75.86,54.31 97.89,45.16 124.61,45.16 C 149.71,45.16 171.91,55.91 189.29,73.47 C 191.95,76.59 193.91,76.67 197.6,74.42 C 204.93,86.26 208.28,98.24 208.98,110.39 C 203.84,111.81 201.28,113.19 202.92,118.61 C 206.51,128.99 207.92,137.93 207.92,148.73 C 207.92,162.11 204.8,174.71 202.84,182.71 C 201.92,186.23 204.31,186.91 210.79,186.99 H 213.91 C 225.87,186.61 235.39,180.64 239.49,169.05 C 244.83,155.26 245.08,138.01 241.66,125.01 Z\"/>\n <path d=\"M 124.71,70.81 C 79.63,70.81 47.49,107.12 47.49,148.72 C 47.49,169.16 56.41,188.19 73.71,200.61 L 73.96,200.77 C 77.43,177.81 84.16,162.21 95.29,145.42 C 92.04,137.09 91.71,130.87 92.62,120.83 C 93.81,111.23 96.08,107.12 102.19,107.68 C 110.04,108.87 117.98,116.18 126.22,124.96 C 139.89,124.05 157.81,131.25 178.33,139.09 C 183.11,140.86 183.72,142.63 182.97,148.33 C 179.45,160.93 170.86,166.63 158.11,174.05 C 143.09,183.07 141.13,199.42 153.88,220.09 L 153.96,221.01 L 154.71,221.17 C 181.31,208.43 201.34,181.01 201.34,148.72 C 201.34,108.15 169.29,70.81 124.71,70.81 Z\"/>\n </svg>\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <div class=\"flex items-center gap-2 px-3 py-2\" v-if=\"!user || !hasAPIKey\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"inline-flex items-center gap-2 rounded-md px-3 py-2 text-base font-medium text-content-secondary hover:bg-muted\"\n aria-label=\"Toggle dark mode\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n <span>{{ darkMode ? 'Light mode' : 'Dark mode' }}</span>\n </button>\n </div>\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat') && supportsAI\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Chat</a>\n <span v-else-if=\"hasAccess(roles, 'chat') && !supportsAI\"\n class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\"\n :title=\"chatDisabledReason\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasTaskVisualizer\"\n href=\"#/tasks\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"taskView ? 'text-content bg-primary-subtle' : 'text-content-secondary hover:bg-muted'\">Tasks</a>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-muted text-content px-3 py-2 text-base font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-page rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-content font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <button\n type=\"button\"\n @click=\"toggleDarkMode\"\n class=\"w-full inline-flex items-center gap-2 px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle text-left\">\n <svg v-if=\"darkMode\" xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z\"/></svg>\n <svg v-else xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path d=\"M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z\"/></svg>\n {{ darkMode ? 'Light mode' : 'Dark mode' }}\n </button>\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <router-link v-if=\"mcpEnabled\" to=\"/mcp\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle\">MCP</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\" title=\"MCP is disabled for this deployment\">\n MCP\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-content-secondary hover:bg-primary-subtle cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n\n<modal\n v-if=\"showRecentPagesModal\"\n flush-container\n containerClass=\"!max-w-md !w-[min(94vw,28rem)] !overflow-hidden !rounded-lg !border !border-edge !bg-surface !shadow-xl dark:!bg-shark-900 text-content\"\n bodyClass=\"modal-recent-pages-body !p-0\"\n>\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeRecentPagesModal\" role=\"button\" aria-label=\"Close recent pages\">&times;</div>\n <div\n class=\"shrink-0 flex items-center gap-3 border-b border-edge bg-surface px-4 py-4 pr-12 dark:bg-shark-900\"\n >\n <h2 class=\"m-0 text-lg font-semibold text-content\">Recent pages</h2>\n <button\n type=\"button\"\n class=\"rounded px-2 py-1 text-sm font-medium text-content-secondary hover:bg-muted disabled:opacity-50\"\n @click=\"clearRecentPagesHistory\"\n :disabled=\"recentPagesList.length === 0\"\n >\n Clear\n </button>\n </div>\n <div class=\"modal-recent-pages-scroll\">\n <div\n v-if=\"recentPagesList.length === 0\"\n class=\"m-4 rounded border border-dashed border-edge px-3 py-6 text-center text-sm text-content-tertiary\"\n >\n No recent pages yet.\n </div>\n <button\n v-for=\"entry in recentPagesList\"\n :key=\"entry.path + '-' + entry.visitedAt\"\n type=\"button\"\n class=\"w-full px-4 py-2.5 text-left hover:bg-muted\"\n :class=\"entry.path === $route.fullPath ? 'bg-primary-subtle' : ''\"\n @click=\"goToRecentPage(entry)\"\n >\n <div class=\"truncate text-sm font-medium text-content\">{{ entry.label }}</div>\n <div class=\"truncate font-mono text-xs text-content-tertiary\">{{ entry.path }}</div>\n </button>\n </div>\n </template>\n</modal>\n</div>\n";
54427
55035
 
54428
55036
  /***/ },
54429
55037
 
@@ -54438,6 +55046,17 @@ module.exports = "<modal v-if=\"show\" containerClass=\"!max-w-md\">\n <templat
54438
55046
 
54439
55047
  /***/ },
54440
55048
 
55049
+ /***/ "./frontend/src/script/script.html"
55050
+ /*!*****************************************!*\
55051
+ !*** ./frontend/src/script/script.html ***!
55052
+ \*****************************************/
55053
+ (module) {
55054
+
55055
+ "use strict";
55056
+ module.exports = "<div class=\"px-4 py-6\">\n <div v-if=\"status === 'loading'\" class=\"max-w-5xl mx-auto text-center\">\n <img src=\"images/loader.gif\" class=\"inline mt-10\" alt=\"Loading script\">\n </div>\n\n <div v-else-if=\"script\" class=\"max-w-5xl mx-auto\">\n <div class=\"flex items-center justify-between mb-4\">\n <div>\n <h1 class=\"text-xl font-semibold text-content\">Script</h1>\n <p class=\"text-xs text-content-tertiary font-mono\">{{ script._id }}</p>\n </div>\n <div class=\"flex items-center\">\n <button\n v-if=\"!isEditing\"\n class=\"px-3 py-1.5 mr-2 text-sm bg-gray-700 text-white rounded hover:bg-gray-600\"\n @click=\"startEditing\">\n Edit\n </button>\n <div class=\"relative flex items-center\">\n <async-button\n class=\"px-3 py-1.5 text-sm bg-primary hover:bg-primary-hover text-primary-text rounded-l-md\"\n @click=\"executeScript\">\n {{ selectedRunLabel }}\n </async-button>\n <button\n class=\"px-1.5 py-1.5 text-sm bg-primary hover:bg-primary-hover text-primary-text border-l border-black/10 rounded-r-md\"\n @click.stop=\"showRunDropdown = !showRunDropdown\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div\n v-if=\"showRunDropdown\"\n class=\"absolute right-0 top-full z-10 mt-1 w-40 rounded-md bg-surface py-1 shadow-lg ring-1 ring-black/5\">\n <button class=\"block w-full px-4 py-2 text-left text-xs text-content-secondary hover:bg-muted\" @click=\"selectRunMode('run')\">\n Run\n </button>\n <button\n class=\"block w-full px-4 py-2 text-left text-xs\"\n :class=\"canUseDryRun ? 'text-content-secondary hover:bg-muted' : 'cursor-not-allowed text-gray-400'\"\n :disabled=\"!canUseDryRun\"\n :title=\"canUseDryRun ? null : 'Dry run mode requires MongoDB transaction support'\"\n @click=\"selectRunMode('dryRun')\">\n Dry Run\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"relative border rounded bg-surface text-content text-sm overflow-hidden\">\n <div class=\"flex border-b py-1 pl-1 text-xs font-medium bg-surface\">\n <button\n class=\"px-4 py-1 rounded-l-md border-r border-edge-strong font-semibold\"\n :class=\"activeTab === 'code' ? 'bg-gray-700 text-white shadow' : 'bg-muted text-gray-600'\"\n @click=\"activeTab = 'code'\">\n Code\n </button>\n <button\n class=\"px-4 py-1 rounded-r-md font-semibold\"\n :class=\"activeTab === 'output' ? 'bg-gray-700 text-white shadow' : 'bg-muted text-gray-600'\"\n @click=\"activeTab = 'output'\">\n Output<span v-if=\"isDryRunResult\" class=\"ml-1\">[Dry Run]</span>\n </button>\n </div>\n\n <div v-show=\"activeTab === 'code'\" class=\"max-h-[65vh] overflow-y-auto\">\n <div v-if=\"isEditing\" class=\"flex flex-col\">\n <ace-editor\n v-model=\"editedScript\"\n mode=\"javascript\"\n :line-numbers=\"true\"\n class=\"w-full h-[55vh] min-h-[300px]\"\n />\n <div class=\"flex justify-end p-2 border-t border-edge\">\n <button class=\"px-3 py-1.5 text-xs bg-gray-300 text-gray-800 rounded hover:bg-gray-400\" @click=\"cancelEditing\">\n Cancel\n </button>\n </div>\n </div>\n <pre v-else class=\"whitespace-pre-wrap !my-0 bg-muted\"><code ref=\"code\" class=\"language-javascript\" v-text=\"script.script\"></code></pre>\n </div>\n\n <div v-show=\"activeTab === 'output'\" class=\"p-3 max-h-[65vh] overflow-y-auto border-t\">\n <div v-if=\"script.executionResult?.error\" class=\"rounded-md bg-red-50 p-4 mb-3 text-sm text-red-800\">\n {{ script.executionResult.error }}\n </div>\n <dashboard-result\n v-if=\"script.executionResult?.output != null\"\n :result=\"script.executionResult.output\">\n </dashboard-result>\n <pre v-else-if=\"!script.executionResult?.error\">No output</pre>\n\n <div v-if=\"script.executionResult?.logs\" class=\"mt-3 pt-3 border-t border-edge\">\n <div class=\"text-xs font-semibold text-gray-600 uppercase tracking-wide\">Console</div>\n <pre class=\"mt-1 bg-muted text-content p-3 rounded whitespace-pre-wrap overflow-x-auto max-h-[280px]\">{{ script.executionResult.logs }}</pre>\n </div>\n </div>\n </div>\n </div>\n\n <div v-else class=\"max-w-5xl mx-auto text-content-secondary\">\n No script with the given id could be found.\n </div>\n</div>\n";
55057
+
55058
+ /***/ },
55059
+
54441
55060
  /***/ "./frontend/src/splash/splash.html"
54442
55061
  /*!*****************************************!*\
54443
55062
  !*** ./frontend/src/splash/splash.html ***!
@@ -54526,6 +55145,17 @@ module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2 flex flex-col gap-8\"
54526
55145
 
54527
55146
  /***/ },
54528
55147
 
55148
+ /***/ "./frontend/src/tooltip/tooltip.html"
55149
+ /*!*******************************************!*\
55150
+ !*** ./frontend/src/tooltip/tooltip.html ***!
55151
+ \*******************************************/
55152
+ (module) {
55153
+
55154
+ "use strict";
55155
+ module.exports = "<span\n ref=\"trigger\"\n class=\"inline-flex outline-none focus-visible:rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600\"\n tabindex=\"0\"\n :aria-describedby=\"isOpen ? tooltipId : null\"\n @mouseenter=\"show\"\n @mouseleave=\"hideUnlessPinned\"\n @focusin=\"show\"\n @focusout=\"handleFocusOut\"\n @click=\"toggle\"\n @keydown.esc.stop=\"hide\"\n>\n <slot></slot>\n <span\n v-show=\"isOpen\"\n ref=\"tooltip\"\n :id=\"tooltipId\"\n class=\"pointer-events-none fixed z-[1000] w-max max-w-[min(20rem,calc(100vw-1rem))] whitespace-normal rounded-md bg-gray-900 px-2.5 py-1.5 text-xs font-normal leading-4 text-white shadow-lg\"\n :style=\"tooltipStyle\"\n role=\"tooltip\"\n >\n {{ text }}\n <span\n class=\"absolute left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 bg-gray-900\"\n :class=\"placement === 'bottom' ? '-top-1' : '-bottom-1'\"\n ></span>\n </span>\n</span>\n";
55156
+
55157
+ /***/ },
55158
+
54529
55159
  /***/ "./frontend/src/update-document/update-document.css"
54530
55160
  /*!**********************************************************!*\
54531
55161
  !*** ./frontend/src/update-document/update-document.css ***!
@@ -59997,7 +60627,7 @@ exports.validatorErrorSymbol = Symbol('mongoose#validatorError');
59997
60627
 
59998
60628
 
59999
60629
 
60000
- const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongodb/lib/bson.js").ObjectId);
60630
+ const ObjectId = (__webpack_require__(/*! mongodb/lib/bson */ "./node_modules/mongoose/node_modules/mongodb/lib/bson.js").ObjectId);
60001
60631
  const objectIdSymbol = (__webpack_require__(/*! ../helpers/symbols */ "./node_modules/mongoose/lib/helpers/symbols.js").objectIdSymbol);
60002
60632
 
60003
60633
  /**
@@ -62994,11 +63624,20 @@ class ObjectId extends BSONValue {
62994
63624
 
62995
63625
  function internalCalculateObjectSize(object, serializeFunctions, ignoreUndefined) {
62996
63626
  const objectStack = [
62997
- { obj: object, ignoreUndefined: ignoreUndefined ?? false }
63627
+ { object, ignoreUndefined: ignoreUndefined ?? false, exit: false }
62998
63628
  ];
63629
+ const path = new Set();
62999
63630
  let total = 0;
63000
63631
  while (objectStack.length > 0) {
63001
- const { obj, ignoreUndefined: frameIgnoreUndefined } = objectStack.pop();
63632
+ const frame = objectStack.pop();
63633
+ if (frame.exit) {
63634
+ path.delete(frame.object);
63635
+ continue;
63636
+ }
63637
+ const { object: obj, ignoreUndefined: frameIgnoreUndefined } = frame;
63638
+ path.add(obj);
63639
+ frame.exit = true;
63640
+ objectStack.push(frame);
63002
63641
  total += 5;
63003
63642
  const isObjArray = Array.isArray(obj);
63004
63643
  const isObjMap = !isObjArray && (obj instanceof Map || isMap(obj));
@@ -63009,23 +63648,23 @@ function internalCalculateObjectSize(object, serializeFunctions, ignoreUndefined
63009
63648
  if (isObjArray) {
63010
63649
  const array = target;
63011
63650
  for (let i = 0; i < array.length; i++) {
63012
- total += calculateElementSize(i.toString(), array[i], serializeFunctions, true, frameIgnoreUndefined, objectStack);
63651
+ total += calculateElementSize(i.toString(), array[i], serializeFunctions, true, frameIgnoreUndefined, objectStack, path);
63013
63652
  }
63014
63653
  }
63015
63654
  else if (isObjMap) {
63016
63655
  for (const [key, value] of target) {
63017
- total += calculateElementSize(key, value, serializeFunctions, false, frameIgnoreUndefined, objectStack);
63656
+ total += calculateElementSize(key, value, serializeFunctions, false, frameIgnoreUndefined, objectStack, path);
63018
63657
  }
63019
63658
  }
63020
63659
  else {
63021
63660
  for (const key of Object.keys(target)) {
63022
- total += calculateElementSize(key, target[key], serializeFunctions, false, frameIgnoreUndefined, objectStack);
63661
+ total += calculateElementSize(key, target[key], serializeFunctions, false, frameIgnoreUndefined, objectStack, path);
63023
63662
  }
63024
63663
  }
63025
63664
  }
63026
63665
  return total;
63027
63666
  }
63028
- function calculateElementSize(name, value, serializeFunctions = false, isArray = false, ignoreUndefined = false, objectStack) {
63667
+ function calculateElementSize(name, value, serializeFunctions = false, isArray = false, ignoreUndefined = false, objectStack, path) {
63029
63668
  if (typeof value?.toBSON === 'function') {
63030
63669
  value = value.toBSON();
63031
63670
  }
@@ -63085,7 +63724,10 @@ function calculateElementSize(name, value, serializeFunctions = false, isArray =
63085
63724
  }
63086
63725
  else if (value._bsontype === 'Code') {
63087
63726
  if (value.scope != null && Object.keys(value.scope).length > 0) {
63088
- objectStack.push({ obj: value.scope, ignoreUndefined });
63727
+ if (path.has(value.scope)) {
63728
+ throw new BSONError('Cannot convert circular structure to BSON');
63729
+ }
63730
+ objectStack.push({ object: value.scope, ignoreUndefined, exit: false });
63089
63731
  return (ByteUtils.utf8ByteLength(name) +
63090
63732
  1 +
63091
63733
  1 +
@@ -63123,7 +63765,7 @@ function calculateElementSize(name, value, serializeFunctions = false, isArray =
63123
63765
  if (value.db != null) {
63124
63766
  ordered_values['$db'] = value.db;
63125
63767
  }
63126
- objectStack.push({ obj: ordered_values, ignoreUndefined: true });
63768
+ objectStack.push({ object: ordered_values, ignoreUndefined: true, exit: false });
63127
63769
  return ByteUtils.utf8ByteLength(name) + 1 + 1;
63128
63770
  }
63129
63771
  else if (value instanceof RegExp || isRegExp(value)) {
@@ -63147,7 +63789,10 @@ function calculateElementSize(name, value, serializeFunctions = false, isArray =
63147
63789
  1);
63148
63790
  }
63149
63791
  else {
63150
- objectStack.push({ obj: value, ignoreUndefined });
63792
+ if (path.has(value)) {
63793
+ throw new BSONError('Cannot convert circular structure to BSON');
63794
+ }
63795
+ objectStack.push({ object: value, ignoreUndefined, exit: false });
63151
63796
  return ByteUtils.utf8ByteLength(name) + 1 + 1;
63152
63797
  }
63153
63798
  case 'function':
@@ -66010,7 +66655,7 @@ var src_default = VueToastificationPlugin;
66010
66655
  (module) {
66011
66656
 
66012
66657
  "use strict";
66013
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.5.13","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","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","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"@nestjs/common":"10.x || 11.x","mongoose":"7.x || 8.x || ^9.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","@nestjs/common":"^11.1.9","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","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","eval:agent":"node eval/agent/run.js"}}');
66658
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.6.0","description":"AI-Powered Data Workspace for MongoDB: Turn your MongoDB data into dashboards, maps, and interactive workflows - powered by your Mongoose models and AI.","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","@modelcontextprotocol/sdk":"^1.30.1","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.1","marked":"15.0.12","mpath":"^0.9.0","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","time-commando":"1.0.1","typescript":"^6.0.2","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15","zod":"^4.6.5"},"peerDependencies":{"@nestjs/common":"10.x || 11.x","mongoose":"7.x || 8.x || ^9.0.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":true}},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","@nestjs/common":"^11.1.9","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","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","eval:agent":"node eval/agent/run.js"}}');
66014
66659
 
66015
66660
  /***/ }
66016
66661
 
@@ -66051,75 +66696,46 @@ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version"
66051
66696
  /******/
66052
66697
  /************************************************************************/
66053
66698
  /******/ /* webpack/runtime/amd define */
66054
- /******/ (() => {
66055
- /******/ __webpack_require__.amdD = function () {
66056
- /******/ throw new Error('define cannot be used indirect');
66057
- /******/ };
66058
- /******/ })();
66699
+ /******/ __webpack_require__.amdD = function () {
66700
+ /******/ throw new Error('define cannot be used indirect');
66701
+ /******/ };
66059
66702
  /******/
66060
66703
  /******/ /* webpack/runtime/define property getters */
66061
- /******/ (() => {
66062
- /******/ // define getter/value functions for harmony exports
66063
- /******/ __webpack_require__.d = (exports, definition) => {
66064
- /******/ if(Array.isArray(definition)) {
66065
- /******/ var i = 0;
66066
- /******/ while(i < definition.length) {
66067
- /******/ var key = definition[i++];
66068
- /******/ var binding = definition[i++];
66069
- /******/ if(!__webpack_require__.o(exports, key)) {
66070
- /******/ if(binding === 0) {
66071
- /******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] });
66072
- /******/ } else {
66073
- /******/ Object.defineProperty(exports, key, { enumerable: true, get: binding });
66074
- /******/ }
66075
- /******/ } else if(binding === 0) { i++; }
66076
- /******/ }
66077
- /******/ } else {
66078
- /******/ for(var key in definition) {
66079
- /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
66080
- /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
66081
- /******/ }
66082
- /******/ }
66704
+ /******/ // define getter/value functions for harmony exports
66705
+ /******/ __webpack_require__.d = (exports, definition) => {
66706
+ /******/ for(var key in definition) {
66707
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
66708
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
66083
66709
  /******/ }
66084
- /******/ };
66085
- /******/ })();
66710
+ /******/ }
66711
+ /******/ };
66086
66712
  /******/
66087
66713
  /******/ /* webpack/runtime/global */
66088
- /******/ (() => {
66089
- /******/ __webpack_require__.g = (function() {
66090
- /******/ if (typeof globalThis === 'object') return globalThis;
66091
- /******/ try {
66092
- /******/ return this || new Function('return this')();
66093
- /******/ } catch (e) {
66094
- /******/ if (typeof window === 'object') return window;
66095
- /******/ }
66096
- /******/ })();
66714
+ /******/ __webpack_require__.g = (function() {
66715
+ /******/ if (typeof globalThis === 'object') return globalThis;
66716
+ /******/ try {
66717
+ /******/ return this || new Function('return this')();
66718
+ /******/ } catch (e) {
66719
+ /******/ if (typeof window === 'object') return window;
66720
+ /******/ }
66097
66721
  /******/ })();
66098
66722
  /******/
66099
66723
  /******/ /* webpack/runtime/hasOwnProperty shorthand */
66100
- /******/ (() => {
66101
- /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
66102
- /******/ })();
66724
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop));
66103
66725
  /******/
66104
66726
  /******/ /* webpack/runtime/make namespace object */
66105
- /******/ (() => {
66106
- /******/ // define __esModule on exports
66107
- /******/ __webpack_require__.r = (exports) => {
66108
- /******/ if(Symbol.toStringTag) {
66109
- /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
66110
- /******/ }
66111
- /******/ Object.defineProperty(exports, '__esModule', { value: true });
66112
- /******/ };
66113
- /******/ })();
66727
+ /******/ // define __esModule on exports
66728
+ /******/ __webpack_require__.r = (exports) => {
66729
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
66730
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
66731
+ /******/ };
66114
66732
  /******/
66115
66733
  /******/ /* webpack/runtime/node module decorator */
66116
- /******/ (() => {
66117
- /******/ __webpack_require__.nmd = (module) => {
66118
- /******/ module.paths = [];
66119
- /******/ if (!module.children) module.children = [];
66120
- /******/ return module;
66121
- /******/ };
66122
- /******/ })();
66734
+ /******/ __webpack_require__.nmd = (module) => {
66735
+ /******/ module.paths = [];
66736
+ /******/ if (!module.children) module.children = [];
66737
+ /******/ return module;
66738
+ /******/ };
66123
66739
  /******/
66124
66740
  /************************************************************************/
66125
66741
  /******/