one 1.6.10 → 1.6.12

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 (80) hide show
  1. package/devtools/devtools.mjs +322 -69
  2. package/dist/cjs/cli/buildPage.cjs +40 -12
  3. package/dist/cjs/cli/buildPage.js +43 -11
  4. package/dist/cjs/cli/buildPage.js.map +1 -1
  5. package/dist/cjs/cli/buildPage.native.js +41 -12
  6. package/dist/cjs/cli/buildPage.native.js.map +1 -1
  7. package/dist/cjs/createHandleRequest.cjs +24 -1
  8. package/dist/cjs/createHandleRequest.js +18 -1
  9. package/dist/cjs/createHandleRequest.js.map +1 -1
  10. package/dist/cjs/createHandleRequest.native.js +24 -1
  11. package/dist/cjs/createHandleRequest.native.js.map +1 -1
  12. package/dist/cjs/router/router.cjs +10 -3
  13. package/dist/cjs/router/router.js +9 -3
  14. package/dist/cjs/router/router.js.map +1 -1
  15. package/dist/cjs/router/router.native.js +7 -0
  16. package/dist/cjs/router/router.native.js.map +1 -1
  17. package/dist/cjs/server/oneServe.cjs +1 -0
  18. package/dist/cjs/server/oneServe.js +2 -0
  19. package/dist/cjs/server/oneServe.js.map +1 -1
  20. package/dist/cjs/server/oneServe.native.js +1 -0
  21. package/dist/cjs/server/oneServe.native.js.map +1 -1
  22. package/dist/cjs/useLoader.cjs +31 -0
  23. package/dist/cjs/useLoader.js +31 -1
  24. package/dist/cjs/useLoader.js.map +1 -1
  25. package/dist/cjs/useLoader.native.js +46 -0
  26. package/dist/cjs/useLoader.native.js.map +1 -1
  27. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +1 -1
  28. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.js +2 -1
  29. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.js.map +1 -1
  30. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +1 -1
  31. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  32. package/dist/esm/cli/buildPage.js +43 -11
  33. package/dist/esm/cli/buildPage.js.map +1 -1
  34. package/dist/esm/cli/buildPage.mjs +40 -12
  35. package/dist/esm/cli/buildPage.mjs.map +1 -1
  36. package/dist/esm/cli/buildPage.native.js +41 -12
  37. package/dist/esm/cli/buildPage.native.js.map +1 -1
  38. package/dist/esm/createHandleRequest.js +18 -1
  39. package/dist/esm/createHandleRequest.js.map +1 -1
  40. package/dist/esm/createHandleRequest.mjs +24 -1
  41. package/dist/esm/createHandleRequest.mjs.map +1 -1
  42. package/dist/esm/createHandleRequest.native.js +24 -1
  43. package/dist/esm/createHandleRequest.native.js.map +1 -1
  44. package/dist/esm/router/router.js +9 -3
  45. package/dist/esm/router/router.js.map +1 -1
  46. package/dist/esm/router/router.mjs +10 -3
  47. package/dist/esm/router/router.mjs.map +1 -1
  48. package/dist/esm/router/router.native.js +7 -0
  49. package/dist/esm/router/router.native.js.map +1 -1
  50. package/dist/esm/server/oneServe.js +2 -0
  51. package/dist/esm/server/oneServe.js.map +1 -1
  52. package/dist/esm/server/oneServe.mjs +1 -0
  53. package/dist/esm/server/oneServe.mjs.map +1 -1
  54. package/dist/esm/server/oneServe.native.js +1 -0
  55. package/dist/esm/server/oneServe.native.js.map +1 -1
  56. package/dist/esm/useLoader.js +31 -0
  57. package/dist/esm/useLoader.js.map +1 -1
  58. package/dist/esm/useLoader.mjs +31 -0
  59. package/dist/esm/useLoader.mjs.map +1 -1
  60. package/dist/esm/useLoader.native.js +46 -0
  61. package/dist/esm/useLoader.native.js.map +1 -1
  62. package/dist/esm/vite/plugins/fileSystemRouterPlugin.js +2 -1
  63. package/dist/esm/vite/plugins/fileSystemRouterPlugin.js.map +1 -1
  64. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +1 -1
  65. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
  66. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +1 -1
  67. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  68. package/package.json +9 -9
  69. package/src/cli/buildPage.ts +67 -15
  70. package/src/createHandleRequest.ts +27 -1
  71. package/src/router/router.ts +29 -0
  72. package/src/server/oneServe.ts +7 -0
  73. package/src/useLoader.ts +58 -0
  74. package/src/vite/plugins/fileSystemRouterPlugin.tsx +7 -0
  75. package/types/cli/buildPage.d.ts.map +1 -1
  76. package/types/createHandleRequest.d.ts.map +1 -1
  77. package/types/router/router.d.ts.map +1 -1
  78. package/types/server/oneServe.d.ts.map +1 -1
  79. package/types/useLoader.d.ts.map +1 -1
  80. package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
@@ -1,5 +1,5 @@
1
1
  // One DevTools - All panels in one draggable window
2
- // Alt+Space opens Spotlight-style quick picker
2
+ // Alt+Space opens searchable hot menu
3
3
 
4
4
  ;(function () {
5
5
  try {
@@ -14,10 +14,132 @@
14
14
  const panelPos = { x: 20, y: 20 }
15
15
  let snappedEdge = { h: null, v: null }
16
16
  let removalObserver = null
17
+ let selectedIndex = 0
18
+ let filteredItems = []
17
19
 
18
20
  const LOGO_SVG =
19
21
  '<svg width="24" height="24" viewBox="0 0 100 100"><circle cx="50" cy="50" r="48" fill="#FCD34D" stroke="#222" stroke-width="2"/><circle cx="50" cy="35" r="16" fill="white"/><text x="50" y="41" text-anchor="middle" font-family="system-ui" font-size="16" font-weight="bold" fill="#222">1</text></svg>'
20
22
 
23
+ const allItems = [
24
+ { id: 'seo', name: 'SEO Preview', type: 'panel', category: 'Panels' },
25
+ { id: 'route', name: 'Route Info', type: 'panel', category: 'Panels' },
26
+ { id: 'loader', name: 'Loader Timing', type: 'panel', category: 'Panels' },
27
+ { id: 'errors', name: 'Errors', type: 'panel', category: 'Panels' },
28
+ { id: 'inspect', name: 'Inspect Source', type: 'tool', category: 'Panels' },
29
+ { id: 'clear-cookies', name: 'Clear Cookies', type: 'action', category: 'Clear Data' },
30
+ {
31
+ id: 'clear-localstorage',
32
+ name: 'Clear localStorage',
33
+ type: 'action',
34
+ category: 'Clear Data',
35
+ },
36
+ {
37
+ id: 'clear-sessionstorage',
38
+ name: 'Clear sessionStorage',
39
+ type: 'action',
40
+ category: 'Clear Data',
41
+ },
42
+ { id: 'clear-indexeddb', name: 'Clear IndexedDB', type: 'action', category: 'Clear Data' },
43
+ {
44
+ id: 'clear-caches',
45
+ name: 'Clear Cache Storage',
46
+ type: 'action',
47
+ category: 'Clear Data',
48
+ },
49
+ {
50
+ id: 'clear-sw',
51
+ name: 'Clear Service Workers',
52
+ type: 'action',
53
+ category: 'Clear Data',
54
+ },
55
+ { id: 'clear-all', name: 'Clear All', type: 'action', category: 'Clear Data' },
56
+ { id: 'reload', name: 'Reload Page', type: 'action', category: 'Actions' },
57
+ { id: 'hard-reload', name: 'Hard Reload', type: 'action', category: 'Actions' },
58
+ { id: 'copy-url', name: 'Copy Current URL', type: 'action', category: 'Actions' },
59
+ { id: 'copy-route', name: 'Copy Route Info', type: 'action', category: 'Actions' },
60
+ ]
61
+
62
+ const itemActions = {
63
+ 'clear-cookies': async () => {
64
+ document.cookie.split(';').forEach((cookie) => {
65
+ const name = (cookie.split('=')[0] || '').trim()
66
+ if (!name) return
67
+ const d = location.hostname
68
+ document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/'
69
+ document.cookie =
70
+ name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=' + d
71
+ document.cookie =
72
+ name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=.' + d
73
+ })
74
+ },
75
+ 'clear-localstorage': async () => {
76
+ localStorage.clear()
77
+ },
78
+ 'clear-sessionstorage': async () => {
79
+ sessionStorage.clear()
80
+ },
81
+ 'clear-indexeddb': async () => {
82
+ if ('indexedDB' in window && typeof indexedDB.databases === 'function') {
83
+ const dbs = await indexedDB.databases()
84
+ await Promise.all(
85
+ dbs.map((db) =>
86
+ db.name
87
+ ? new Promise((resolve) => {
88
+ const req = indexedDB.deleteDatabase(db.name)
89
+ req.onsuccess = resolve
90
+ req.onerror = resolve
91
+ req.onblocked = resolve
92
+ })
93
+ : Promise.resolve()
94
+ )
95
+ )
96
+ }
97
+ },
98
+ 'clear-caches': async () => {
99
+ if ('caches' in window) {
100
+ const names = await caches.keys()
101
+ await Promise.all(names.map((n) => caches.delete(n)))
102
+ }
103
+ },
104
+ 'clear-sw': async () => {
105
+ if ('serviceWorker' in navigator) {
106
+ const regs = await navigator.serviceWorker.getRegistrations()
107
+ await Promise.all(regs.map((r) => r.unregister()))
108
+ }
109
+ },
110
+ 'clear-all': async () => {
111
+ await itemActions['clear-cookies']()
112
+ await itemActions['clear-localstorage']()
113
+ await itemActions['clear-sessionstorage']()
114
+ await itemActions['clear-indexeddb']()
115
+ await itemActions['clear-caches']()
116
+ await itemActions['clear-sw']()
117
+ },
118
+ reload: async () => {
119
+ location.reload()
120
+ },
121
+ 'hard-reload': async () => {
122
+ if ('caches' in window) {
123
+ const names = await caches.keys()
124
+ await Promise.all(names.map((n) => caches.delete(n)))
125
+ }
126
+ location.reload()
127
+ },
128
+ 'copy-url': async () => {
129
+ await navigator.clipboard.writeText(location.href)
130
+ },
131
+ 'copy-route': async () => {
132
+ const dt = window.__oneDevtools || {}
133
+ const info = {
134
+ pathname: location.pathname,
135
+ search: location.search,
136
+ hash: location.hash,
137
+ params: dt.routeInfo?.params || {},
138
+ }
139
+ await navigator.clipboard.writeText(JSON.stringify(info, null, 2))
140
+ },
141
+ }
142
+
21
143
  function escapeHtml(str) {
22
144
  if (!str) return ''
23
145
  return String(str)
@@ -39,15 +161,23 @@
39
161
  dialog::backdrop { background: transparent; }
40
162
  #spotlight-dialog::backdrop { background: rgba(0,0,0,0.3); backdrop-filter: blur(8px); }
41
163
  .spotlight { display: flex; align-items: center; justify-content: center; position: fixed; inset: 0; }
42
- .spotlight-box { background: #1a1a1a; border-radius: 12px; width: 320px; max-width: 90vw; overflow: hidden; box-shadow: 0 16px 48px rgba(0,0,0,0.5); }
43
- .spotlight-header { display: flex; align-items: center; padding: 12px 16px; border-bottom: 1px solid #252525; gap: 10px; }
164
+ .spotlight-box { background: #1a1a1a; border-radius: 12px; width: 340px; max-width: 90vw; overflow: hidden; box-shadow: 0 16px 48px rgba(0,0,0,0.5); display: flex; flex-direction: column; }
165
+ .spotlight-header { display: flex; align-items: center; padding: 12px 14px; border-bottom: 1px solid #252525; gap: 10px; flex-shrink: 0; }
44
166
  .spotlight-header svg { width: 20px; height: 20px; flex-shrink: 0; }
45
- .spotlight-header-title { font: 13px system-ui, sans-serif; color: #ccc; flex: 1; }
46
- .spotlight-header-version { font: 11px system-ui, sans-serif; color: #666; }
47
- .spotlight-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; color: #ccc; font: 13px system-ui, sans-serif; cursor: pointer; transition: background 0.1s; border-bottom: 1px solid #252525; }
48
- .spotlight-item:last-child { border-bottom: none; }
49
- .spotlight-item:hover { background: #252525; color: #fff; }
50
- .spotlight-item .key { background: #333; color: #888; padding: 2px 6px; border-radius: 4px; font-size: 11px; margin-left: auto; }
167
+ .spotlight-search { flex: 1; background: transparent; border: none; color: #eee; font: 14px system-ui, sans-serif; outline: none; min-width: 0; }
168
+ .spotlight-search::placeholder { color: #555; }
169
+ .spotlight-items { max-height: 354px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #333 transparent; }
170
+ .spotlight-items::-webkit-scrollbar { width: 4px; }
171
+ .spotlight-items::-webkit-scrollbar-track { background: transparent; }
172
+ .spotlight-items::-webkit-scrollbar-thumb { background: #333; border-radius: 2px; }
173
+ .spotlight-category { font: 10px system-ui, sans-serif; color: #505050; text-transform: uppercase; letter-spacing: 0.5px; padding: 10px 16px 4px; user-select: none; }
174
+ .spotlight-item { display: flex; align-items: center; padding: 10px 16px; color: #aaa; font: 13px system-ui, sans-serif; cursor: pointer; transition: background 0.08s; }
175
+ .spotlight-item:hover, .spotlight-item.selected { background: #252525; color: #fff; }
176
+ .spotlight-item .name { flex: 1; }
177
+ .spotlight-item .status { font-size: 11px; color: #666; margin-left: 8px; }
178
+ .spotlight-item .status.ok { color: #4ade80; }
179
+ .spotlight-item .status.err { color: #f87171; }
180
+ .spotlight-empty { text-align: center; color: #444; padding: 24px; font: 13px system-ui, sans-serif; }
51
181
  .panel-dialog { position: fixed; margin: 0; inset: unset; z-index: 2147483647; }
52
182
  .panel-dialog::backdrop { display: none; }
53
183
  .panel { width: 420px; max-width: calc(100vw - 40px); max-height: calc(100vh - 40px); background: #161616; border-radius: 10px; box-shadow: 0 12px 40px rgba(0,0,0,0.4); display: flex; flex-direction: column; overflow: hidden; font: 13px system-ui, sans-serif; color: #ccc; }
@@ -90,7 +220,15 @@
90
220
  shadow.innerHTML =
91
221
  '<style>' +
92
222
  css +
93
- '</style><dialog id="spotlight-dialog"><div class="spotlight"><div class="spotlight-box" id="spotlight-box"></div></div></dialog><dialog class="panel-dialog" id="panel-dialog"><div class="panel" id="panel"></div></dialog>'
223
+ '</style>' +
224
+ '<dialog id="spotlight-dialog"><div class="spotlight"><div class="spotlight-box" id="spotlight-box">' +
225
+ '<div class="spotlight-header">' +
226
+ LOGO_SVG +
227
+ '<input type="text" class="spotlight-search" id="spotlight-search" placeholder="Search actions..." autocomplete="off" spellcheck="false" />' +
228
+ '</div>' +
229
+ '<div class="spotlight-items" id="spotlight-items"></div>' +
230
+ '</div></div></dialog>' +
231
+ '<dialog class="panel-dialog" id="panel-dialog"><div class="panel" id="panel"></div></dialog>'
94
232
  document.body.appendChild(host)
95
233
 
96
234
  setupSpotlight()
@@ -121,51 +259,180 @@
121
259
  removalObserver.observe(document.body, { childList: true })
122
260
  }
123
261
 
262
+ function fuzzyScore(raw, query) {
263
+ // light fuzzy: prefer matching at word starts
264
+ const text = raw.toLowerCase().replace(/\s+/g, '')
265
+ const words = raw.toLowerCase().split(/\s+/)
266
+ const wordStarts = new Set()
267
+ let pos = 0
268
+ for (const w of words) {
269
+ wordStarts.add(pos)
270
+ pos += w.length
271
+ }
272
+ let ti = 0
273
+ let score = 0
274
+ for (let qi = 0; qi < query.length; qi++) {
275
+ const ch = query[qi]
276
+ // scan for first match, but prefer word-start match
277
+ let first = -1
278
+ for (let j = ti; j < text.length; j++) {
279
+ if (text[j] === ch) {
280
+ if (wordStarts.has(j)) { first = j; break }
281
+ if (first === -1) first = j
282
+ }
283
+ }
284
+ if (first === -1) return -1
285
+ score += wordStarts.has(first) ? 10 : 1
286
+ ti = first + 1
287
+ }
288
+ return score
289
+ }
290
+
291
+ function renderSpotlightItems(query) {
292
+ const q = (query || '').toLowerCase().replace(/\s+/g, '')
293
+ const scored = []
294
+ for (const item of allItems) {
295
+ if (!q) {
296
+ scored.push({ item, score: 0 })
297
+ continue
298
+ }
299
+ const ns = fuzzyScore(item.name, q)
300
+ const cs = fuzzyScore(item.category, q)
301
+ const best = Math.max(ns, cs)
302
+ if (best >= 0) scored.push({ item, score: best })
303
+ }
304
+ scored.sort((a, b) => b.score - a.score)
305
+ filteredItems = scored.map((s) => s.item)
306
+ selectedIndex = filteredItems.length > 0 ? 0 : -1
307
+
308
+ const container = shadow.getElementById('spotlight-items')
309
+ if (!filteredItems.length) {
310
+ container.innerHTML = '<div class="spotlight-empty">No matching actions</div>'
311
+ return
312
+ }
313
+
314
+ let html = ''
315
+ let lastCategory = ''
316
+ filteredItems.forEach((item, index) => {
317
+ if (item.category !== lastCategory) {
318
+ lastCategory = item.category
319
+ html +=
320
+ '<div class="spotlight-category">' + escapeHtml(item.category) + '</div>'
321
+ }
322
+ html +=
323
+ '<div class="spotlight-item' +
324
+ (index === selectedIndex ? ' selected' : '') +
325
+ '" data-id="' +
326
+ item.id +
327
+ '" data-index="' +
328
+ index +
329
+ '"><span class="name">' +
330
+ escapeHtml(item.name) +
331
+ '</span><span class="status"></span></div>'
332
+ })
333
+
334
+ container.innerHTML = html
335
+ }
336
+
337
+ function updateSelection() {
338
+ if (!shadow) return
339
+ shadow.querySelectorAll('.spotlight-item').forEach((el) => {
340
+ const idx = parseInt(el.dataset.index)
341
+ el.classList.toggle('selected', idx === selectedIndex)
342
+ })
343
+ const selected = shadow.querySelector('.spotlight-item.selected')
344
+ if (selected) selected.scrollIntoView({ block: 'nearest' })
345
+ }
346
+
347
+ function handleItemClick(itemId) {
348
+ const item = allItems.find((i) => i.id === itemId)
349
+ if (!item) return
350
+
351
+ if (item.type === 'panel') {
352
+ hideSpotlight()
353
+ activeTab = item.id
354
+ showPanel()
355
+ return
356
+ }
357
+
358
+ if (item.id === 'inspect') {
359
+ hideSpotlight()
360
+ window.__oneSourceInspector?.activate()
361
+ return
362
+ }
363
+
364
+ const actionFn = itemActions[item.id]
365
+ if (!actionFn) return
366
+
367
+ const el = shadow.querySelector('.spotlight-item[data-id="' + item.id + '"]')
368
+ const statusEl = el?.querySelector('.status')
369
+ if (statusEl) statusEl.textContent = '...'
370
+
371
+ actionFn()
372
+ .then(() => {
373
+ if (statusEl) {
374
+ statusEl.textContent = '\u2713'
375
+ statusEl.className = 'status ok'
376
+ }
377
+ setTimeout(hideSpotlight, 500)
378
+ })
379
+ .catch((err) => {
380
+ if (statusEl) {
381
+ statusEl.textContent = '\u2717'
382
+ statusEl.className = 'status err'
383
+ }
384
+ console.error('[One DevTools]', err)
385
+ })
386
+ }
387
+
124
388
  function setupSpotlight() {
125
389
  spotlightDialog = shadow.getElementById('spotlight-dialog')
126
390
  const box = shadow.getElementById('spotlight-box')
391
+ const searchInput = shadow.getElementById('spotlight-search')
392
+ const itemsContainer = shadow.getElementById('spotlight-items')
127
393
 
128
- const items = [
129
- { id: 'seo', name: 'SEO Preview', key: '⌥S' },
130
- { id: 'route', name: 'Route Info', key: '⌥R' },
131
- { id: 'loader', name: 'Loader Timing', key: '⌥L' },
132
- { id: 'errors', name: 'Errors', key: '⌥E' },
133
- { id: 'inspect', name: 'Inspect Source', key: '⌥I' },
134
- ]
394
+ searchInput.addEventListener('input', () => {
395
+ renderSpotlightItems(searchInput.value)
396
+ })
135
397
 
136
- const header =
137
- '<div class="spotlight-header">' +
138
- LOGO_SVG +
139
- '<span class="spotlight-header-title">Dev Tools</span><span class="spotlight-header-version">v1.2.57</span></div>'
140
- box.innerHTML =
141
- header +
142
- items
143
- .map(
144
- (item) =>
145
- '<div class="spotlight-item" data-tab="' +
146
- item.id +
147
- '"><span>' +
148
- item.name +
149
- '</span><span class="key">' +
150
- item.key +
151
- '</span></div>'
152
- )
153
- .join('')
398
+ box.addEventListener('keydown', (e) => {
399
+ if (e.key === 'ArrowDown') {
400
+ e.preventDefault()
401
+ if (filteredItems.length) {
402
+ selectedIndex = (selectedIndex + 1) % filteredItems.length
403
+ updateSelection()
404
+ }
405
+ } else if (e.key === 'ArrowUp') {
406
+ e.preventDefault()
407
+ if (filteredItems.length) {
408
+ selectedIndex =
409
+ selectedIndex <= 0 ? filteredItems.length - 1 : selectedIndex - 1
410
+ updateSelection()
411
+ }
412
+ } else if (e.key === 'Enter') {
413
+ e.preventDefault()
414
+ if (selectedIndex >= 0 && selectedIndex < filteredItems.length) {
415
+ handleItemClick(filteredItems[selectedIndex].id)
416
+ }
417
+ }
418
+ })
154
419
 
155
- box.addEventListener('click', (e) => {
420
+ itemsContainer.addEventListener('mousemove', (e) => {
156
421
  const item = e.target.closest('.spotlight-item')
157
422
  if (item) {
158
- const tab = item.dataset.tab
159
- hideSpotlight()
160
- if (tab === 'inspect') {
161
- window.__oneSourceInspector?.activate()
162
- } else {
163
- activeTab = tab
164
- showPanel()
423
+ const idx = parseInt(item.dataset.index)
424
+ if (!isNaN(idx) && idx !== selectedIndex) {
425
+ selectedIndex = idx
426
+ updateSelection()
165
427
  }
166
428
  }
167
429
  })
168
430
 
431
+ itemsContainer.addEventListener('click', (e) => {
432
+ const item = e.target.closest('.spotlight-item')
433
+ if (item) handleItemClick(item.dataset.id)
434
+ })
435
+
169
436
  spotlightDialog.addEventListener('click', (e) => {
170
437
  if (e.target === spotlightDialog) spotlightDialog.close()
171
438
  })
@@ -177,7 +444,7 @@
177
444
  panel.innerHTML =
178
445
  '<div class="panel-header" id="panel-header">' +
179
446
  LOGO_SVG +
180
- '<span class="panel-title">DevTools</span><button class="panel-close" id="panel-close">×</button></div><div class="tabs" id="tabs"><button class="tab active" data-tab="seo">SEO</button><button class="tab" data-tab="route">Route</button><button class="tab" data-tab="loader">Loader</button><button class="tab" data-tab="errors">Errors</button></div><div class="content" id="content"></div>'
447
+ '<span class="panel-title">DevTools</span><button class="panel-close" id="panel-close">\u00d7</button></div><div class="tabs" id="tabs"><button class="tab active" data-tab="seo">SEO</button><button class="tab" data-tab="route">Route</button><button class="tab" data-tab="loader">Loader</button><button class="tab" data-tab="errors">Errors</button></div><div class="content" id="content"></div>'
181
448
 
182
449
  shadow.getElementById('panel-close').addEventListener('click', hidePanel)
183
450
 
@@ -263,36 +530,18 @@
263
530
  function setupKeyboard() {
264
531
  if (keyboardSetup) return
265
532
  keyboardSetup = true
266
- // use capture: false so app handlers can preventDefault first
267
533
  document.addEventListener(
268
534
  'keydown',
269
535
  (e) => {
270
- // respect other handlers and browser shortcuts
271
536
  if (e.defaultPrevented || e.metaKey || e.ctrlKey) return
272
537
 
273
538
  if (e.altKey && e.code === 'Space') {
274
539
  e.preventDefault()
275
540
  toggleSpotlight()
276
- } else if (e.altKey) {
277
- if (e.code === 'KeyI') {
278
- e.preventDefault()
279
- hideSpotlight()
280
- window.__oneSourceInspector?.activate()
281
- return
282
- }
283
- const tabMap = {
284
- KeyS: 'seo',
285
- KeyR: 'route',
286
- KeyL: 'loader',
287
- KeyE: 'errors',
288
- }
289
- const tab = tabMap[e.code]
290
- if (tab) {
291
- e.preventDefault()
292
- activeTab = tab
293
- hideSpotlight()
294
- showPanel()
295
- }
541
+ } else if (e.altKey && e.code === 'KeyI') {
542
+ e.preventDefault()
543
+ hideSpotlight()
544
+ window.__oneSourceInspector?.activate()
296
545
  } else if (e.code === 'Escape') {
297
546
  if (spotlightDialog?.open) spotlightDialog.close()
298
547
  else if (panelDialog?.open) panelDialog.close()
@@ -309,11 +558,15 @@
309
558
 
310
559
  function showSpotlight() {
311
560
  if (!host) createHost()
561
+ const searchInput = shadow.getElementById('spotlight-search')
562
+ searchInput.value = ''
563
+ renderSpotlightItems('')
312
564
  spotlightDialog.showModal()
565
+ requestAnimationFrame(() => searchInput.focus())
313
566
  }
314
567
 
315
568
  function hideSpotlight() {
316
- spotlightDialog.close()
569
+ if (spotlightDialog?.open) spotlightDialog.close()
317
570
  }
318
571
 
319
572
  function showPanel() {
@@ -391,7 +644,7 @@
391
644
  issues.forEach((i) => {
392
645
  html +=
393
646
  '<div class="issue-item"><span class="issue-icon">' +
394
- (i.type === 'error' ? '' : '') +
647
+ (i.type === 'error' ? '\u2716' : '\u26a0') +
395
648
  '</span><span>' +
396
649
  escapeHtml(i.msg) +
397
650
  '</span></div>'
@@ -591,7 +844,7 @@
591
844
  const errors = devtools.errorHistory || []
592
845
 
593
846
  if (!errors.length) {
594
- return '<div class="empty">✓ No errors recorded</div>'
847
+ return '<div class="empty">\u2713 No errors recorded</div>'
595
848
  }
596
849
 
597
850
  let html = ''
@@ -187,24 +187,34 @@ prefetchCSS()
187
187
  loaderData: result.loaderData
188
188
  });
189
189
  }
190
- if (recordTiming("layoutLoaders", performance.now() - t0), t0 = performance.now(), exported.loader) {
190
+ recordTiming("layoutLoaders", performance.now() - t0), t0 = performance.now();
191
+ let loaderRedirectInfo = null;
192
+ if (exported.loader) {
191
193
  try {
192
194
  loaderData = (await exported.loader?.(loaderProps)) ?? null;
193
195
  } catch (err) {
194
- if (!(0, import_isResponse.isResponse)(err)) throw err;
196
+ if ((0, import_isResponse.isResponse)(err)) loaderRedirectInfo = extractRedirectInfo(err);else throw err;
195
197
  }
196
- if (clientJsPath) {
197
- const code = await readFile(clientJsPath, "utf-8"),
198
- withLoader =
199
- // super dirty to quickly make ssr loaders work until we have better
200
- `
198
+ if (!loaderRedirectInfo && loaderData && ((0, import_isResponse.isResponse)(loaderData) || loaderData instanceof Response || loaderData?.constructor?.name === "Response") && (loaderRedirectInfo = extractRedirectInfo(loaderData), loaderData = {}), clientJsPath) {
199
+ const loaderPartialPath = (0, import_node_path.join)(clientDir, urlPathToFilePath((0, import_cleanUrl.getLoaderPath)(path)));
200
+ if (loaderRedirectInfo) {
201
+ const redirectData = JSON.stringify({
202
+ __oneRedirect: loaderRedirectInfo.path,
203
+ __oneRedirectStatus: loaderRedirectInfo.status
204
+ });
205
+ await outputFile(loaderPartialPath, `export function loader(){return ${redirectData}}`), loaderPath = (0, import_cleanUrl.getLoaderPath)(path), loaderData = {};
206
+ } else {
207
+ const code = await readFile(clientJsPath, "utf-8"),
208
+ withLoader =
209
+ // super dirty to quickly make ssr loaders work until we have better
210
+ `
201
211
  if (typeof document === 'undefined') globalThis.document = {}
202
212
  ` + (0, import_replaceLoader.replaceLoader)({
203
- code,
204
- loaderData
205
- }),
206
- loaderPartialPath = (0, import_node_path.join)(clientDir, urlPathToFilePath((0, import_cleanUrl.getLoaderPath)(path)));
207
- await outputFile(loaderPartialPath, withLoader), loaderPath = (0, import_cleanUrl.getLoaderPath)(path);
213
+ code,
214
+ loaderData
215
+ });
216
+ await outputFile(loaderPartialPath, withLoader), loaderPath = (0, import_cleanUrl.getLoaderPath)(path);
217
+ }
208
218
  }
209
219
  }
210
220
  if (recordTiming("pageLoader", performance.now() - t0), matches.push({
@@ -303,6 +313,24 @@ async function getRender(serverEntry) {
303
313
  function removeTrailingSlash(path) {
304
314
  return path.endsWith("/") ? path.slice(0, path.length - 1) : path;
305
315
  }
316
+ function extractRedirectInfo(response) {
317
+ if (response.status >= 300 && response.status < 400) {
318
+ const location = response.headers.get("location");
319
+ if (location) try {
320
+ const url = new URL(location);
321
+ return {
322
+ path: url.pathname + url.search + url.hash,
323
+ status: response.status
324
+ };
325
+ } catch {
326
+ return {
327
+ path: location,
328
+ status: response.status
329
+ };
330
+ }
331
+ }
332
+ return null;
333
+ }
306
334
  function applyAfterLCPScriptLoad(html, preloads) {
307
335
  html = html.replace(/<script\s+type="module"[^>]*async[^>]*><\/script>/gi, "");
308
336
  const loaderScript = `
@@ -165,24 +165,40 @@ prefetchCSS()
165
165
  loaderData: result.loaderData
166
166
  });
167
167
  }
168
- if (recordTiming("layoutLoaders", performance.now() - t0), t0 = performance.now(), exported.loader) {
168
+ recordTiming("layoutLoaders", performance.now() - t0), t0 = performance.now();
169
+ let loaderRedirectInfo = null;
170
+ if (exported.loader) {
169
171
  try {
170
172
  loaderData = await exported.loader?.(loaderProps) ?? null;
171
173
  } catch (err) {
172
- if (!(0, import_isResponse.isResponse)(err))
174
+ if ((0, import_isResponse.isResponse)(err))
175
+ loaderRedirectInfo = extractRedirectInfo(err);
176
+ else
173
177
  throw err;
174
178
  }
175
- if (clientJsPath) {
176
- const code = await readFile(clientJsPath, "utf-8"), withLoader = (
177
- // super dirty to quickly make ssr loaders work until we have better
178
- `
179
+ if (!loaderRedirectInfo && loaderData && ((0, import_isResponse.isResponse)(loaderData) || loaderData instanceof Response || loaderData?.constructor?.name === "Response") && (loaderRedirectInfo = extractRedirectInfo(loaderData), loaderData = {}), clientJsPath) {
180
+ const loaderPartialPath = (0, import_node_path.join)(clientDir, urlPathToFilePath((0, import_cleanUrl.getLoaderPath)(path)));
181
+ if (loaderRedirectInfo) {
182
+ const redirectData = JSON.stringify({
183
+ __oneRedirect: loaderRedirectInfo.path,
184
+ __oneRedirectStatus: loaderRedirectInfo.status
185
+ });
186
+ await outputFile(
187
+ loaderPartialPath,
188
+ `export function loader(){return ${redirectData}}`
189
+ ), loaderPath = (0, import_cleanUrl.getLoaderPath)(path), loaderData = {};
190
+ } else {
191
+ const code = await readFile(clientJsPath, "utf-8"), withLoader = (
192
+ // super dirty to quickly make ssr loaders work until we have better
193
+ `
179
194
  if (typeof document === 'undefined') globalThis.document = {}
180
195
  ` + (0, import_replaceLoader.replaceLoader)({
181
- code,
182
- loaderData
183
- })
184
- ), loaderPartialPath = (0, import_node_path.join)(clientDir, urlPathToFilePath((0, import_cleanUrl.getLoaderPath)(path)));
185
- await outputFile(loaderPartialPath, withLoader), loaderPath = (0, import_cleanUrl.getLoaderPath)(path);
196
+ code,
197
+ loaderData
198
+ })
199
+ );
200
+ await outputFile(loaderPartialPath, withLoader), loaderPath = (0, import_cleanUrl.getLoaderPath)(path);
201
+ }
186
202
  }
187
203
  }
188
204
  if (recordTiming("pageLoader", performance.now() - t0), matches.push({
@@ -280,6 +296,22 @@ async function getRender(serverEntry) {
280
296
  function removeTrailingSlash(path) {
281
297
  return path.endsWith("/") ? path.slice(0, path.length - 1) : path;
282
298
  }
299
+ function extractRedirectInfo(response) {
300
+ if (response.status >= 300 && response.status < 400) {
301
+ const location = response.headers.get("location");
302
+ if (location)
303
+ try {
304
+ const url = new URL(location);
305
+ return {
306
+ path: url.pathname + url.search + url.hash,
307
+ status: response.status
308
+ };
309
+ } catch {
310
+ return { path: location, status: response.status };
311
+ }
312
+ }
313
+ return null;
314
+ }
283
315
  function applyAfterLCPScriptLoad(html, preloads) {
284
316
  html = html.replace(/<script\s+type="module"[^>]*async[^>]*><\/script>/gi, "");
285
317
  const loaderScript = `