webspresso 0.0.110 → 0.0.112

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.
@@ -8,7 +8,7 @@
8
8
  * and lifecycle cleanup errors without logging raw form values or sensitive payloads.
9
9
  */
10
10
 
11
- const { NOOP_DEBUGGER } = require('./utils');
11
+ const { isArray, isNumber, NOOP_DEBUGGER, uid } = require('./utils');
12
12
 
13
13
  const SENSITIVE_KEY_PATTERN = /password|passwd|secret|token|authorization|cookie|credit|card|cvv|ssn|api[-_]?key/i;
14
14
 
@@ -32,7 +32,7 @@ function summarizePayloadSafely(payload, includeSafePrimitives = false) {
32
32
  if (typeof Blob !== 'undefined' && payload instanceof Blob) {
33
33
  return { type: 'Blob', size: payload.size, mimeType: payload.type || 'application/octet-stream' };
34
34
  }
35
- if (Array.isArray(payload)) {
35
+ if (isArray(payload)) {
36
36
  return { type: 'Array', length: payload.length };
37
37
  }
38
38
  if (typeof payload === 'object') {
@@ -58,7 +58,7 @@ function summarizePayloadSafely(payload, includeSafePrimitives = false) {
58
58
  */
59
59
  function createActiveDebugger(options = {}) {
60
60
  let enabled = Boolean(options.enabled);
61
- const maxEvents = typeof options.maxEvents === 'number' && options.maxEvents > 0 ? options.maxEvents : 200;
61
+ const maxEvents = isNumber(options.maxEvents) && options.maxEvents > 0 ? options.maxEvents : 200;
62
62
  const customLogger = typeof options.logger === 'function' ? options.logger : null;
63
63
 
64
64
  const events = [];
@@ -73,7 +73,7 @@ function createActiveDebugger(options = {}) {
73
73
 
74
74
  function pushEvent(type, detail = {}) {
75
75
  const entry = {
76
- id: `evt_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
76
+ id: uid('evt'),
77
77
  type,
78
78
  timestamp: Date.now(),
79
79
  ...detail,
@@ -192,7 +192,7 @@ function createActiveDebugger(options = {}) {
192
192
  completedAt: Date.now(),
193
193
  },
194
194
  invalidation: {
195
- tags: Array.isArray(tags) ? [...tags] : [],
195
+ tags: isArray(tags) ? [...tags] : [],
196
196
  triggeredAt: Date.now(),
197
197
  },
198
198
  refresh: null,
@@ -215,11 +215,11 @@ function createActiveDebugger(options = {}) {
215
215
  recordChainRefresh(chainId, { fragmentIds, status, loadMs, renderMs, totalServerMs, error }) {
216
216
  const chain = chainId ? causalChains.get(chainId) : null;
217
217
  const refreshData = {
218
- fragmentIds: Array.isArray(fragmentIds) ? [...fragmentIds] : [],
218
+ fragmentIds: isArray(fragmentIds) ? [...fragmentIds] : [],
219
219
  status: status || 'ok',
220
- loadMs: typeof loadMs === 'number' ? loadMs : null,
221
- renderMs: typeof renderMs === 'number' ? renderMs : null,
222
- totalServerMs: typeof totalServerMs === 'number' ? totalServerMs : null,
220
+ loadMs: isNumber(loadMs) ? loadMs : null,
221
+ renderMs: isNumber(renderMs) ? renderMs : null,
222
+ totalServerMs: isNumber(totalServerMs) ? totalServerMs : null,
223
223
  error: error ? String(error.message || error) : null,
224
224
  completedAt: Date.now(),
225
225
  };
@@ -234,9 +234,9 @@ function createActiveDebugger(options = {}) {
234
234
  const patchData = {
235
235
  instanceId,
236
236
  fragmentName: fragmentName || null,
237
- patchMs: typeof patchMs === 'number' ? patchMs : 0,
238
- loadMs: typeof loadMs === 'number' ? loadMs : null,
239
- renderMs: typeof renderMs === 'number' ? renderMs : null,
237
+ patchMs: isNumber(patchMs) ? patchMs : 0,
238
+ loadMs: isNumber(loadMs) ? loadMs : null,
239
+ renderMs: isNumber(renderMs) ? renderMs : null,
240
240
  completedAt: Date.now(),
241
241
  };
242
242
  if (chain) {
@@ -15,16 +15,15 @@
15
15
  */
16
16
 
17
17
  const { defaultStorage } = require('./storage');
18
+ const { isArray, isNumber } = require('./utils');
18
19
 
19
20
  const DEVTOOL_HOST_ID = 'webspresso-client-devtools-host';
20
21
 
21
- const PKG_VERSION = (() => {
22
- try {
23
- return require('../../package.json').version || '0.0.110';
24
- } catch (_) {
25
- return '0.0.110';
26
- }
27
- })();
22
+ const PKG_VERSION =
23
+ (typeof window !== 'undefined' &&
24
+ window.__WEBSPRESSO_DEV_META__ &&
25
+ window.__WEBSPRESSO_DEV_META__.version) ||
26
+ '0.0.110';
28
27
 
29
28
  /**
30
29
  * Connect to Redux DevTools browser extension if installed.
@@ -176,10 +175,61 @@ function mountDevtool(runtime, options = {}) {
176
175
  const position = options.position || 'bottom-right';
177
176
  const isRight = position.includes('right');
178
177
  const studioUrl = options.studioUrl || 'http://localhost:4567';
178
+ let metaOverrides =
179
+ options.meta && typeof options.meta === 'object' ? { ...options.meta } : {};
179
180
  let isOpen = Boolean(options.open);
180
181
  let activeTab = 'controllers'; // 'controllers' | 'fragments' | 'network' | 'storage' | 'studio'
181
182
  let selectedControllerId = null;
182
183
 
184
+ function resolveDevFeatures() {
185
+ const winMeta =
186
+ typeof window !== 'undefined' &&
187
+ window.__WEBSPRESSO_DEV_META__ &&
188
+ typeof window.__WEBSPRESSO_DEV_META__ === 'object'
189
+ ? window.__WEBSPRESSO_DEV_META__
190
+ : {};
191
+ const merged = { ...winMeta, ...metaOverrides };
192
+ const pluginsList = isArray(merged.plugins)
193
+ ? merged.plugins
194
+ : isArray(options.plugins)
195
+ ? options.plugins
196
+ : [];
197
+
198
+ const hasStudio =
199
+ options.hasStudio !== undefined
200
+ ? Boolean(options.hasStudio)
201
+ : merged.hasStudio !== undefined
202
+ ? Boolean(merged.hasStudio)
203
+ : merged.hasDb !== undefined
204
+ ? Boolean(merged.hasDb)
205
+ : Boolean(
206
+ options.studioUrl ||
207
+ pluginsList.includes('studio') ||
208
+ pluginsList.includes('db-studio')
209
+ );
210
+
211
+ const hasAdmin =
212
+ options.hasAdmin !== undefined
213
+ ? Boolean(options.hasAdmin)
214
+ : merged.hasAdmin !== undefined
215
+ ? Boolean(merged.hasAdmin)
216
+ : Boolean(
217
+ options.adminPath ||
218
+ options.adminUrl ||
219
+ pluginsList.includes('admin-panel') ||
220
+ pluginsList.includes('adminPanel') ||
221
+ pluginsList.includes('admin')
222
+ );
223
+
224
+ const adminPath = merged.adminPath || options.adminPath || options.adminUrl || '/_admin';
225
+ return {
226
+ hasStudio,
227
+ hasAdmin,
228
+ adminPath,
229
+ plugins: pluginsList,
230
+ };
231
+ }
232
+
183
233
  // Initialize Redux DevTools extension bridge if available
184
234
  const reduxBridge = setupReduxDevToolsBridge(runtime);
185
235
 
@@ -257,6 +307,8 @@ function mountDevtool(runtime, options = {}) {
257
307
  flex-direction: column;
258
308
  overflow: hidden;
259
309
  z-index: 999999;
310
+ }
311
+ .ws-drawer.ws-drawer-enter {
260
312
  animation: wsFadeIn 0.15s ease-out;
261
313
  }
262
314
  @keyframes wsFadeIn {
@@ -753,33 +805,46 @@ function mountDevtool(runtime, options = {}) {
753
805
  }
754
806
 
755
807
  function renderStudioTab() {
756
- return `
757
- <div>
758
- <div style="margin-bottom:16px;">
759
- <strong style="font-size:13px;color:#fff;">Developer Tools & Studio Integrations</strong>
760
- <p style="font-size:10px;color:#71717a;">Direct shortcuts to Webspresso's integrated developer productivity tools.</p>
761
- </div>
808
+ const features = resolveDevFeatures();
762
809
 
810
+ const studioCardHtml = features.hasStudio
811
+ ? `
763
812
  <div class="ws-card" style="margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;">
764
813
  <div>
765
814
  <div style="font-weight:700;color:#fbbf24;font-size:13px;">🗄️ Webspresso DB Studio</div>
766
815
  <div style="font-size:10px;color:#a1a1aa;margin-top:2px;">Visual database browser, query runner, and table inspector.</div>
767
816
  <div style="font-size:9px;color:#71717a;margin-top:4px;">Run in terminal: <code>npx webspresso studio</code> (port 4567)</div>
768
817
  </div>
769
- <a href="${studioUrl}" target="_blank" rel="noopener noreferrer" class="ws-btn ws-btn-primary" style="text-decoration:none;">
818
+ <a href="${studioUrl}" target="_blank" rel="noopener noreferrer" class="ws-btn ws-btn-primary" id="ws-btn-open-studio" style="text-decoration:none;">
770
819
  Open DB Studio ↗
771
820
  </a>
772
821
  </div>
822
+ `
823
+ : '';
773
824
 
825
+ const adminCardHtml = features.hasAdmin
826
+ ? `
774
827
  <div class="ws-card" style="margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;">
775
828
  <div>
776
829
  <div style="font-weight:700;color:#38bdf8;font-size:13px;">⚙️ Admin Panel SPA</div>
777
830
  <div style="font-size:10px;color:#a1a1aa;margin-top:2px;">Mithril.js model CRUD and staff administration cockpit.</div>
778
831
  </div>
779
- <a href="/_admin" target="_blank" rel="noopener noreferrer" class="ws-btn" style="text-decoration:none;">
780
- Open /_admin ↗
832
+ <a href="${features.adminPath}" target="_blank" rel="noopener noreferrer" class="ws-btn" id="ws-btn-open-admin" style="text-decoration:none;">
833
+ Open ${features.adminPath} ↗
781
834
  </a>
782
835
  </div>
836
+ `
837
+ : '';
838
+
839
+ return `
840
+ <div>
841
+ <div style="margin-bottom:16px;">
842
+ <strong style="font-size:13px;color:#fff;">Developer Tools & Studio Integrations</strong>
843
+ <p style="font-size:10px;color:#71717a;">Direct shortcuts to Webspresso's integrated developer productivity tools.</p>
844
+ </div>
845
+
846
+ ${studioCardHtml}
847
+ ${adminCardHtml}
783
848
 
784
849
  <div class="ws-card" style="display:flex;align-items:center;justify-content:space-between;">
785
850
  <div>
@@ -859,6 +924,9 @@ function mountDevtool(runtime, options = {}) {
859
924
  `;
860
925
  }
861
926
 
927
+ let lastRenderedHtml = '';
928
+ let wasOpen = false;
929
+
862
930
  function findInShadow(id) {
863
931
  if (shadow && typeof shadow.getElementById === 'function') {
864
932
  return shadow.getElementById(id);
@@ -866,8 +934,39 @@ function mountDevtool(runtime, options = {}) {
866
934
  return shadow && typeof shadow.querySelector === 'function' ? shadow.querySelector(`#${id}`) : null;
867
935
  }
868
936
 
869
- function updateDom() {
870
- container.innerHTML = isOpen ? renderDrawer() : renderPill();
937
+ function updateDom(force = false) {
938
+ const nextHtml = isOpen ? renderDrawer() : renderPill();
939
+ if (!force && nextHtml === lastRenderedHtml && isOpen === wasOpen) {
940
+ return;
941
+ }
942
+
943
+ // Preserve scroll positions inside drawer before updating DOM
944
+ let prevContentScrollTop = 0;
945
+ const prevContentEl =
946
+ isOpen && wasOpen && shadow && typeof shadow.querySelector === 'function'
947
+ ? shadow.querySelector('.ws-content')
948
+ : null;
949
+ if (prevContentEl) {
950
+ prevContentScrollTop = prevContentEl.scrollTop || 0;
951
+ }
952
+
953
+ lastRenderedHtml = nextHtml;
954
+ const openingTransition = isOpen && !wasOpen;
955
+ wasOpen = isOpen;
956
+
957
+ container.innerHTML = nextHtml;
958
+ if (openingTransition) {
959
+ const drawerEl = findInShadow('ws-devtool-drawer');
960
+ if (drawerEl && drawerEl.classList) {
961
+ drawerEl.classList.add('ws-drawer-enter');
962
+ }
963
+ }
964
+ if (prevContentScrollTop > 0 && shadow && typeof shadow.querySelector === 'function') {
965
+ const nextContentEl = shadow.querySelector('.ws-content');
966
+ if (nextContentEl) {
967
+ nextContentEl.scrollTop = prevContentScrollTop;
968
+ }
969
+ }
871
970
  bindEvents();
872
971
  }
873
972
 
@@ -1043,7 +1142,7 @@ function mountDevtool(runtime, options = {}) {
1043
1142
  document.addEventListener('keydown', onKeyDown);
1044
1143
 
1045
1144
  // Periodic polling to keep snapshots live
1046
- const pollIntervalMs = typeof options.pollInterval === 'number' ? options.pollInterval : 2000;
1145
+ const pollIntervalMs = isNumber(options.pollInterval) ? options.pollInterval : 2000;
1047
1146
  let pollTimer = null;
1048
1147
  if (pollIntervalMs > 0) {
1049
1148
  pollTimer = setInterval(() => {
@@ -1076,6 +1175,12 @@ function mountDevtool(runtime, options = {}) {
1076
1175
  isOpen() {
1077
1176
  return isOpen;
1078
1177
  },
1178
+ setMeta(nextMeta = {}) {
1179
+ if (nextMeta && typeof nextMeta === 'object') {
1180
+ metaOverrides = { ...metaOverrides, ...nextMeta };
1181
+ updateDom();
1182
+ }
1183
+ },
1079
1184
  getSnapshot() {
1080
1185
  return {
1081
1186
  controllers: getControllersSnapshot(),