@necrolab/dashboard 0.5.15 → 0.5.16

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 (121) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +140 -170
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +58 -15
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +12 -20
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -22
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +220 -0
  20. package/src/assets/css/main.scss +66 -77
  21. package/src/components/Auth/LoginForm.vue +5 -84
  22. package/src/components/Editors/Account/Account.vue +8 -10
  23. package/src/components/Editors/Account/AccountCreator.vue +28 -59
  24. package/src/components/Editors/Account/AccountView.vue +38 -86
  25. package/src/components/Editors/Account/CreateAccount.vue +8 -50
  26. package/src/components/Editors/Profile/CreateProfile.vue +74 -131
  27. package/src/components/Editors/Profile/Profile.vue +15 -17
  28. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  29. package/src/components/Editors/Profile/ProfileView.vue +46 -96
  30. package/src/components/Editors/TagLabel.vue +16 -55
  31. package/src/components/Editors/TagToggle.vue +20 -8
  32. package/src/components/Filter/Filter.vue +62 -75
  33. package/src/components/Filter/FilterPreview.vue +161 -135
  34. package/src/components/Filter/PriceSortToggle.vue +36 -43
  35. package/src/components/Table/Header.vue +1 -1
  36. package/src/components/Table/Table.vue +45 -51
  37. package/src/components/Tasks/CheckStock.vue +7 -16
  38. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  39. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  40. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  41. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  42. package/src/components/Tasks/EventDetailRow.vue +21 -0
  43. package/src/components/Tasks/MassEdit.vue +6 -16
  44. package/src/components/Tasks/QuickSettings.vue +140 -216
  45. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  46. package/src/components/Tasks/Stats.vue +19 -38
  47. package/src/components/Tasks/Task.vue +65 -268
  48. package/src/components/Tasks/TaskLabel.vue +9 -3
  49. package/src/components/Tasks/TaskView.vue +43 -63
  50. package/src/components/Tasks/Utilities.vue +10 -44
  51. package/src/components/Tasks/ViewTask.vue +23 -107
  52. package/src/components/icons/Close.vue +2 -8
  53. package/src/components/icons/Gear.vue +8 -8
  54. package/src/components/icons/Hash.vue +5 -0
  55. package/src/components/icons/Key.vue +2 -8
  56. package/src/components/icons/Pencil.vue +2 -8
  57. package/src/components/icons/Profile.vue +2 -8
  58. package/src/components/icons/Sell.vue +2 -8
  59. package/src/components/icons/Spinner.vue +4 -7
  60. package/src/components/icons/SquareCheck.vue +2 -8
  61. package/src/components/icons/SquareUncheck.vue +2 -8
  62. package/src/components/icons/Wildcard.vue +2 -8
  63. package/src/components/icons/index.js +3 -1
  64. package/src/components/ui/ActionButtonGroup.vue +113 -52
  65. package/src/components/ui/BalanceIndicator.vue +60 -0
  66. package/src/components/ui/EmptyState.vue +24 -0
  67. package/src/components/ui/EnableDisableToggle.vue +23 -0
  68. package/src/components/ui/FormField.vue +48 -48
  69. package/src/components/ui/IconLabel.vue +23 -0
  70. package/src/components/ui/InfoRow.vue +21 -54
  71. package/src/components/ui/Modal.vue +89 -56
  72. package/src/components/ui/Navbar.vue +60 -41
  73. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  74. package/src/components/ui/ReconnectIndicator.vue +111 -124
  75. package/src/components/ui/SectionCard.vue +6 -14
  76. package/src/components/ui/Splash.vue +2 -10
  77. package/src/components/ui/StatusBadge.vue +26 -28
  78. package/src/components/ui/TaskToggle.vue +54 -0
  79. package/src/components/ui/controls/CountryChooser.vue +27 -64
  80. package/src/components/ui/controls/EyeToggle.vue +1 -1
  81. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  82. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  83. package/src/components/ui/controls/atomic/MultiDropdown.vue +71 -119
  84. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  85. package/src/composables/useColorMapping.js +15 -0
  86. package/src/composables/useCopyToClipboard.js +1 -1
  87. package/src/composables/useDateFormatting.js +21 -0
  88. package/src/composables/useDeviceDetection.js +14 -0
  89. package/src/composables/useDropdownPosition.js +3 -4
  90. package/src/composables/useDynamicTableHeight.js +31 -0
  91. package/src/composables/useRowSelection.js +0 -3
  92. package/src/composables/useTicketPricing.js +16 -0
  93. package/src/composables/useWindowDimensions.js +21 -0
  94. package/src/libs/Filter.js +14 -20
  95. package/src/libs/panzoom.js +1 -5
  96. package/src/libs/utils/array.js +60 -0
  97. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  98. package/src/libs/utils/eventUrl.js +40 -0
  99. package/src/libs/utils/string.js +28 -0
  100. package/src/libs/utils/time.js +20 -0
  101. package/src/libs/utils/validation.js +88 -0
  102. package/src/main.js +0 -2
  103. package/src/stores/connection.js +1 -4
  104. package/src/stores/logger.js +6 -12
  105. package/src/stores/sampleData.js +1 -2
  106. package/src/stores/ui.js +59 -36
  107. package/src/views/Accounts.vue +13 -24
  108. package/src/views/Console.vue +70 -172
  109. package/src/views/Editor.vue +211 -379
  110. package/src/views/FilterBuilder.vue +188 -371
  111. package/src/views/Login.vue +3 -28
  112. package/src/views/Profiles.vue +8 -15
  113. package/src/views/Tasks.vue +49 -36
  114. package/tailwind.config.js +82 -71
  115. package/workbox-config.cjs +47 -5
  116. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  117. package/exit +0 -209
  118. package/run +0 -177
  119. package/src/assets/css/base/color-fallbacks.scss +0 -10
  120. package/switch-branch.sh +0 -41
  121. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="console-page">
2
+ <div class="mb-8 pb-16 md:mb-0 md:pb-0 mobile-portrait:mb-12 mobile-portrait:pb-24">
3
3
  <div class="page-header" style="padding-bottom: 0.75rem;">
4
4
  <div class="page-header-card">
5
5
  <ConsoleIcon />
@@ -30,21 +30,23 @@
30
30
  v-model="searchQuery"
31
31
  type="text"
32
32
  placeholder="Search logs..."
33
- class="h-full w-full bg-transparent text-sm text-white outline-none" />
33
+ aria-label="Search logs"
34
+ class="transparent-input" />
34
35
  <span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
35
36
  </div>
36
37
  <!-- Scroll buttons on mobile - inline with search -->
37
38
  <button
38
- class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
39
+ class="console-scroll-btn md:hidden"
39
40
  @mousedown="startScrolling('up')"
40
41
  @mouseup="stopScrolling"
41
42
  @mouseleave="stopScrolling"
42
43
  @touchstart="startScrolling('up')"
43
- @touchend="stopScrolling">
44
+ @touchend="stopScrolling"
45
+ aria-label="Scroll up">
44
46
  <UpIcon class="pointer-events-none h-5 w-5" />
45
47
  </button>
46
48
  <button
47
- class="console-scroll-btn flex h-10 w-10 items-center justify-center rounded border-2 bg-dark-400 shadow-sm md:hidden"
49
+ class="console-scroll-btn md:hidden"
48
50
  @mousedown="startScrolling('down')"
49
51
  @mouseup="stopScrolling"
50
52
  @mouseleave="stopScrolling"
@@ -96,16 +98,15 @@
96
98
 
97
99
  <Smoothie
98
100
  :weight="0.2"
99
- class="console scrollable smooth-scroll overflow-x-auto overflow-y-auto font-mono text-white"
100
- style="min-height: 12rem !important"
101
+ class="console-main"
101
102
  ref="$autoscroll"
102
103
  @wheel.stop
103
104
  @touchmove.stop
104
105
  @scroll="handleScroll">
105
106
  <div
106
107
  v-if="displayedLogs.length === 0"
107
- class="empty-state flex h-full flex-col items-center justify-center text-center">
108
- <ConsoleIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
108
+ class="flex h-full min-h-56 flex-col items-center justify-center text-center font-sans">
109
+ <ConsoleIcon class="empty-state-icon" />
109
110
  <p class="text-sm text-light-400">
110
111
  {{ searchQuery ? "No logs match your search" : "No logs yet" }}
111
112
  </p>
@@ -115,12 +116,12 @@
115
116
  </div>
116
117
  <pre
117
118
  v-else
118
- class="hidden-scrollbars log-entry"
119
+ class="hidden-scrollbars log-entry opacity-0 transition-all duration-150 hover:bg-white/2"
119
120
  v-for="(line, index) in displayedLogs"
120
121
  v-bind:key="`log-${index}`"
121
- :style="{ '--index': index }"><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
122
+ :style="{ '--index': index }"><code class="md:text-sm lg:text-base mobile-portrait:text-xs+ mobile-portrait:leading-tight" v-html="line"></code></pre>
122
123
  </Smoothie>
123
- <div class="console-switches mb-6 mt-4 flex justify-between md:hidden">
124
+ <div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
124
125
  <button
125
126
  class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
126
127
  <h3 class="text-sm text-white">Hide Monitors</h3>
@@ -140,156 +141,57 @@
140
141
  </div>
141
142
  </template>
142
143
  <style lang="scss" scoped>
143
- .console-page {
144
- @apply pb-16 mb-8 md:pb-0 md:mb-0;
145
-
146
- @media (max-width: 480px) and (orientation: portrait) {
147
- @apply pb-24 mb-12;
148
- }
149
- }
150
-
151
- .console-switches {
152
- @media (max-width: 480px) and (orientation: portrait) {
153
- @apply mt-6 mb-16;
154
- }
155
- }
156
-
144
+ /* Webkit scrollbar customization (cannot be done with Tailwind utilities) */
157
145
  .console {
158
- @apply relative rounded border-2 border-dark-550 bg-dark-400 p-2 lg:p-5;
159
- @apply touch-pan-x touch-pan-y;
160
- height: calc(100vh - 18rem);
161
146
  scrollbar-width: thin;
162
- scrollbar-color: oklch(0.35 0 0) oklch(0.19 0 0);
147
+ scrollbar-color: oklch(0.35 0 0) oklch(0.1822 0 0);
163
148
  -webkit-overflow-scrolling: touch;
164
149
 
165
- // Use fixed height on mobile portrait to ensure switches are visible
166
- @media (max-width: 768px) {
167
- max-height: 60vh;
168
- height: auto;
169
- }
170
-
171
- @media (max-width: 480px) and (orientation: portrait) {
172
- max-height: 50vh;
173
- height: auto;
174
- }
175
-
176
- @media (min-width: 769px) and (max-width: 1023px) {
177
- height: calc(100vh - 16rem);
178
- }
179
-
180
- @media (min-width: 1024px) {
181
- height: calc(100vh - 14rem);
182
- }
183
-
184
150
  &::-webkit-scrollbar {
185
- width: 8px;
151
+ @apply w-2;
186
152
  }
187
153
 
188
154
  &::-webkit-scrollbar-track {
189
- background: oklch(0.19 0 0);
190
- border-radius: 4px;
155
+ @apply rounded bg-dark-300;
191
156
  }
192
157
 
193
158
  &::-webkit-scrollbar-thumb {
159
+ @apply rounded transition-colors duration-200;
194
160
  background: oklch(0.35 0 0);
195
- border-radius: 4px;
196
- transition: background-color 0.2s ease;
197
161
  }
198
162
 
199
163
  &::-webkit-scrollbar-thumb:hover {
200
164
  background: oklch(0.45 0 0);
201
165
  }
202
166
 
203
- // Smooth scrolling behavior with momentum
204
167
  &.smooth-scroll {
205
- scroll-behavior: smooth;
168
+ @apply scroll-smooth;
206
169
  scroll-padding: 0.5rem;
207
170
  -webkit-overflow-scrolling: touch;
208
171
  overscroll-behavior: contain;
209
172
  }
210
-
211
- // Improved log entry animations
212
- .log-entry {
213
- opacity: 0;
214
- transform: translateY(4px);
215
- animation: slideInLog 0.2s ease-out forwards;
216
- transition: all 0.15s ease;
217
-
218
- &:hover {
219
- background-color: rgba(255, 255, 255, 0.02);
220
- transform: translateX(2px);
221
- }
222
- }
223
-
224
- // Stagger animation for new logs
225
- .log-entry:last-child {
226
- animation-delay: 0.05s;
227
- }
228
-
229
- @keyframes slideInLog {
230
- to {
231
- opacity: 1;
232
- transform: translateY(0);
233
- }
234
- }
235
-
236
- // Empty state styling
237
- .empty-state {
238
- min-height: 14rem;
239
- font-family:
240
- "Inter",
241
- -apple-system,
242
- BlinkMacSystemFont,
243
- "Segoe UI",
244
- Helvetica,
245
- Arial,
246
- sans-serif;
247
- }
248
-
249
- textarea {
250
- background: transparent;
251
- resize: none;
252
- @apply w-full text-white focus:outline-none;
253
- }
254
173
  }
255
174
 
256
- .console-scroll-btn {
257
- border-color: oklch(0.2809 0 0);
258
- transition: all 0.15s ease;
175
+ /* Animation for log entries (keyframes cannot be done with Tailwind) */
176
+ .log-entry {
177
+ transform: translateY(4px);
178
+ animation: slideInLog 0.2s ease-out forwards;
259
179
 
260
- &:hover,
261
- &:active {
262
- border-color: oklch(0.72 0.15 145) !important;
263
- outline: 1px solid oklch(0.72 0.15 145);
264
- outline-offset: 0;
180
+ &:hover {
181
+ transform: translateX(2px);
265
182
  }
266
183
  }
267
184
 
268
- /* Mobile portrait console optimizations */
269
- @screen mobile-portrait {
270
- .console {
271
- height: calc(100vh - 19.5rem);
272
- @apply p-1 text-xs;
273
- overflow: auto;
274
-
275
- pre {
276
- line-height: 1.2;
277
- }
278
-
279
- code {
280
- font-size: 0.7rem !important;
281
- }
282
- }
185
+ .log-entry:last-child {
186
+ animation-delay: 0.05s;
283
187
  }
284
188
 
285
- /* Mobile landscape console optimizations */
286
- @media (max-width: 1024px) and (orientation: landscape) {
287
- .console {
288
- height: calc(100vh - 10.5rem);
189
+ @keyframes slideInLog {
190
+ to {
191
+ @apply opacity-100;
192
+ transform: translateY(0);
289
193
  }
290
194
  }
291
-
292
- /* Console-specific styles handled by utilities */
293
195
  </style>
294
196
  <script setup>
295
197
  import { Smoothie } from "vue-smoothie";
@@ -301,11 +203,6 @@ import Switch from "@/components/ui/controls/atomic/Switch.vue";
301
203
  import WebsocketHeartbeatJs from "websocket-heartbeat-js";
302
204
  import { onMounted, onUnmounted, ref, nextTick, computed, watch } from "vue";
303
205
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
304
- import { sortAlphaNum } from "@/stores/utils";
305
-
306
- import { useUIStore } from "@/stores/ui";
307
-
308
- const ui = useUIStore();
309
206
 
310
207
  const $autoscroll = ref(null);
311
208
  const logLines = ref([]);
@@ -315,12 +212,13 @@ const taskLogMapping = ref({});
315
212
  const currentTaskLog = ref("");
316
213
  const filteredLogs = ref(true);
317
214
  const userScrolledUp = ref(false);
318
- const lastScrollTime = ref(0);
319
215
  const scrollInterval = ref(null);
320
216
  const isScrolling = ref(false);
321
217
  const searchQuery = ref("");
322
218
 
323
- // Computed filtered logs based on search query
219
+ // Optimized: Cache stripped versions to avoid regex on every filter
220
+ const logPlainTextCache = new Map();
221
+
324
222
  const displayedLogs = computed(() => {
325
223
  let logs =
326
224
  currentTaskLog.value && currentTaskLog.value !== ""
@@ -330,8 +228,12 @@ const displayedLogs = computed(() => {
330
228
  if (searchQuery.value.trim()) {
331
229
  const query = searchQuery.value.toLowerCase();
332
230
  logs = logs.filter((log) => {
333
- // Remove HTML tags for search
334
- const plainText = log.replace(/<[^>]*>/g, "").toLowerCase();
231
+ // Use cached plain text or compute and cache it
232
+ let plainText = logPlainTextCache.get(log);
233
+ if (plainText === undefined) {
234
+ plainText = log.replace(/<[^>]*>/g, "").toLowerCase();
235
+ logPlainTextCache.set(log, plainText);
236
+ }
335
237
  return plainText.includes(query);
336
238
  });
337
239
  }
@@ -346,28 +248,29 @@ const filteredCount = computed(() => {
346
248
 
347
249
  const path = "/api/updates?type=console";
348
250
  const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
349
- // Handle manual scroll detection
251
+
252
+ // Scroll thresholds
253
+ const SCROLL_THRESHOLD = 50; // pixels from bottom to consider "near bottom"
254
+ const SCROLL_AMOUNT = 100; // pixels to scroll per interval
255
+
350
256
  const handleScroll = (event) => {
351
257
  if (!autoscrollToggled.value) return;
352
258
 
353
259
  const element = event.target;
354
260
  if (!element) return;
355
261
 
356
- // Check if user is near bottom (within 50px)
357
- const threshold = 50;
358
- const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - threshold;
262
+ // Check if user is near bottom
263
+ const isNearBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - SCROLL_THRESHOLD;
359
264
 
360
- // Only update if there's an actual change
361
265
  if (userScrolledUp.value === isNearBottom) {
362
266
  userScrolledUp.value = !isNearBottom;
363
267
  }
364
268
  };
365
269
 
366
- // Bulletproof scroll function with multiple fallbacks
367
270
  const performScroll = (direction, smooth = true) => {
368
271
  try {
369
272
  if (!$autoscroll.value?.el) {
370
- if (DEBUG) console.log("Autoscroll element not ready");
273
+ if (DEBUG) return false;
371
274
  return false;
372
275
  }
373
276
 
@@ -391,7 +294,7 @@ const performScroll = (direction, smooth = true) => {
391
294
 
392
295
  return true;
393
296
  } catch (e) {
394
- if (DEBUG) console.log("Error scrolling", e);
297
+ if (DEBUG) return false;
395
298
  return false;
396
299
  }
397
300
  };
@@ -401,64 +304,54 @@ const startScrolling = (direction) => {
401
304
  if (isScrolling.value) return;
402
305
 
403
306
  isScrolling.value = true;
404
-
405
- // Immediate scroll
406
307
  performScroll(direction, true);
407
308
 
408
- // Continue scrolling while held (for long content)
409
- scrollInterval.value = setInterval(() => {
309
+ // Optimized: Use requestAnimationFrame instead of setInterval for smoother scrolling
310
+ const continuousScroll = () => {
410
311
  if (!isScrolling.value) return;
411
312
 
412
313
  const element = $autoscroll.value?.el;
413
314
  if (!element) return;
414
315
 
415
- const scrollAmount = 100;
416
316
  if (direction === "up") {
417
- element.scrollTop = Math.max(0, element.scrollTop - scrollAmount);
317
+ element.scrollTop = Math.max(0, element.scrollTop - SCROLL_AMOUNT);
418
318
  } else {
419
- element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + scrollAmount);
319
+ element.scrollTop = Math.min(element.scrollHeight - element.clientHeight, element.scrollTop + SCROLL_AMOUNT);
420
320
  }
421
- }, 50);
321
+
322
+ scrollInterval.value = requestAnimationFrame(continuousScroll);
323
+ };
324
+
325
+ scrollInterval.value = requestAnimationFrame(continuousScroll);
422
326
  };
423
327
 
424
328
  const stopScrolling = () => {
425
329
  isScrolling.value = false;
426
330
  if (scrollInterval.value) {
427
- clearInterval(scrollInterval.value);
331
+ cancelAnimationFrame(scrollInterval.value);
428
332
  scrollInterval.value = null;
429
333
  }
430
334
  };
431
335
 
432
- // Legacy function for compatibility
433
- const scrollTo = (dir) => {
434
- performScroll(dir, true);
435
- };
436
-
437
- // Simple autoscroll to bottom
438
336
  const autoScrollToBottom = () => {
439
337
  if (!$autoscroll.value?.el || !autoscrollToggled.value) return;
440
338
 
441
339
  // Only scroll if user hasn't manually scrolled up
442
340
  if (!userScrolledUp.value) {
443
341
  const element = $autoscroll.value.el;
444
-
445
- // Calculate the target scroll position to show the last log
446
342
  const targetScrollTop = element.scrollHeight - element.clientHeight;
447
343
 
448
- // Use smooth scrolling to ensure new logs are visible
449
344
  if (element.scrollTo && Math.abs(element.scrollTop - targetScrollTop) > 5) {
450
345
  element.scrollTo({
451
346
  top: targetScrollTop,
452
347
  behavior: "smooth"
453
348
  });
454
349
  } else {
455
- // For small differences or fallback, use instant scroll
456
350
  element.scrollTop = targetScrollTop;
457
351
  }
458
352
  }
459
353
  };
460
354
 
461
- // Handle autoscroll toggle
462
355
  const onAutoscrollToggle = () => {
463
356
  if (autoscrollToggled.value) {
464
357
  userScrolledUp.value = false;
@@ -470,6 +363,10 @@ const addAnsiToOutput = (a) => {
470
363
  const html = ansii.toHtml(a?.log || a);
471
364
  logLines.value.push(html);
472
365
 
366
+ // Optimized: Pre-cache the plain text version when adding logs
367
+ const plainText = html.replace(/<[^>]*>/g, "").toLowerCase();
368
+ logPlainTextCache.set(html, plainText);
369
+
473
370
  // Auto scroll after adding new content with proper timing
474
371
  nextTick().then(() => {
475
372
  // Use a small delay to ensure DOM is fully updated
@@ -497,13 +394,17 @@ const handleWebsocketMessages = (msg) => {
497
394
  const makeTaskLogMapping = (lines) => {
498
395
  lines.forEach((l) => {
499
396
  if (!l.metadata) {
500
- if (DEBUG) console.log("Error getting metadata", l);
501
397
  return;
502
398
  }
503
399
  const region = l.metadata.siteId?.split("_")?.[1];
504
400
  const n = l.metadata.global ? "Global" : `${region}-${l.metadata.taskId}`;
505
401
  if (!taskLogMapping.value[n]) taskLogMapping.value[n] = [];
506
- taskLogMapping.value[n].push(ansii.toHtml(l.log));
402
+ const html = ansii.toHtml(l.log);
403
+ taskLogMapping.value[n].push(html);
404
+
405
+ // Optimized: Pre-cache plain text for task log mapping too
406
+ const plainText = html.replace(/<[^>]*>/g, "").toLowerCase();
407
+ logPlainTextCache.set(html, plainText);
507
408
  });
508
409
  };
509
410
 
@@ -525,7 +426,6 @@ window.startDebugConsoleMessages = () => {
525
426
 
526
427
  if (DEBUG) window.startDebugConsoleMessages();
527
428
 
528
- // Watch for log filter changes and reset scroll state
529
429
  watch([currentTaskLog, filteredLogs], () => {
530
430
  userScrolledUp.value = false;
531
431
  nextTick().then(() => {
@@ -533,20 +433,18 @@ watch([currentTaskLog, filteredLogs], () => {
533
433
  });
534
434
  });
535
435
 
536
- // Listen for messages
537
436
  onMounted(() => {
538
437
  const socket = new WebsocketHeartbeatJs({ url, pingMsg: "ping" });
539
438
 
540
439
  socket.onmessage = (event) => {
541
440
  const msg = JSON.parse(event.data);
542
- if (DEBUG) console.log("Received message", msg);
441
+ ;
543
442
  msg.forEach((e) => {
544
443
  handleWebsocketMessages(e);
545
444
  });
546
445
  };
547
446
  });
548
447
 
549
- // Cleanup on unmount
550
448
  onUnmounted(() => {
551
449
  stopScrolling();
552
450
  });