@hongmaple0820/scale-engine 0.50.1 → 0.50.2

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 (50) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/http.js +3 -1
  4. package/dist/api/http.js.map +1 -1
  5. package/dist/cli/cortexCommands.d.ts +16 -0
  6. package/dist/cli/cortexCommands.js +47 -4
  7. package/dist/cli/cortexCommands.js.map +1 -1
  8. package/dist/cortex/InstinctStore.d.ts +13 -1
  9. package/dist/cortex/InstinctStore.js +90 -11
  10. package/dist/cortex/InstinctStore.js.map +1 -1
  11. package/dist/cortex/SessionInjector.js +39 -2
  12. package/dist/cortex/SessionInjector.js.map +1 -1
  13. package/dist/dashboard/DashboardServer.d.ts +158 -0
  14. package/dist/dashboard/DashboardServer.js +753 -13
  15. package/dist/dashboard/DashboardServer.js.map +1 -1
  16. package/dist/dashboard/spa/assets/index-VYBCLBje.js +11 -0
  17. package/dist/dashboard/spa/assets/index-VhwY_ac1.css +1 -0
  18. package/dist/dashboard/spa/assets/naive-ui-BQy2AJkt.js +3340 -0
  19. package/dist/dashboard/spa/assets/vendor-BPU6aOYA.js +3 -0
  20. package/dist/dashboard/spa/assets/vue-CQQMb5Wi.js +17 -0
  21. package/dist/dashboard/spa/index.html +15 -462
  22. package/dist/memory/MemoryFabric.d.ts +13 -1
  23. package/dist/memory/MemoryFabric.js +60 -0
  24. package/dist/memory/MemoryFabric.js.map +1 -1
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.js +1 -1
  27. package/docs/workflow/ASSESSMENT_INDEX.md +326 -0
  28. package/docs/workflow/COMPARATIVE_ANALYSIS.md +422 -0
  29. package/docs/workflow/EXECUTIVE_SUMMARY.md +310 -0
  30. package/docs/workflow/IMPROVEMENT_CHECKLIST.md +518 -0
  31. package/docs/workflow/IMPROVEMENT_ROADMAP.md +707 -0
  32. package/docs/workflow/README.md +8 -0
  33. package/package.json +6 -2
  34. package/dist/dashboard/spa/app.js +0 -515
  35. package/dist/dashboard/spa/components/DataTable.js +0 -53
  36. package/dist/dashboard/spa/components/EventStream.js +0 -66
  37. package/dist/dashboard/spa/components/LoadingState.js +0 -39
  38. package/dist/dashboard/spa/components/MetricCard.js +0 -30
  39. package/dist/dashboard/spa/components/Panel.js +0 -27
  40. package/dist/dashboard/spa/components/StatusBadge.js +0 -51
  41. package/dist/dashboard/spa/i18n.js +0 -767
  42. package/dist/dashboard/spa/pages/costs.js +0 -522
  43. package/dist/dashboard/spa/pages/documents.js +0 -540
  44. package/dist/dashboard/spa/pages/knowledge.js +0 -457
  45. package/dist/dashboard/spa/pages/monitoring.js +0 -361
  46. package/dist/dashboard/spa/pages/overview.js +0 -301
  47. package/dist/dashboard/spa/pages/topology-renderers.js +0 -251
  48. package/dist/dashboard/spa/pages/topology.js +0 -370
  49. package/dist/dashboard/spa/pages/workflow-renderers.js +0 -239
  50. package/dist/dashboard/spa/pages/workflow.js +0 -217
@@ -1,540 +0,0 @@
1
- /**
2
- * Documents Page v2 - Markdown rendering, full-text search, doc metadata, favorites.
3
- */
4
- ;(() => {
5
- 'use strict'
6
-
7
- const { copyText, downloadText, fetchJSON, formatTime, t, $, $$, dom } = window.Dashboard
8
- const { autoRefreshControl, dataNote, el, emptyState, panel, renderText } = dom
9
-
10
- let currentDoc = null
11
- let allDocs = []
12
- let favorites = new Set(JSON.parse(localStorage.getItem('scale-doc-favorites') || '[]'))
13
-
14
- async function renderDocuments() {
15
- const app = $('#app')
16
- const search = el('input', {
17
- id: 'doc-search',
18
- type: 'text',
19
- className: 'search-box',
20
- placeholder: t('documents.searchPlaceholder'),
21
- style: { flex: '1', maxWidth: '400px' },
22
- })
23
- const refresh = el('button', { id: 'doc-refresh', className: 'topo-btn', text: t('common.refresh') })
24
- const copyIndex = el('button', { id: 'doc-copy-index', className: 'topo-btn', text: t('documents.copyIndex') })
25
- const downloadIndex = el('button', { id: 'doc-download-index', className: 'topo-btn', text: t('documents.downloadIndex') })
26
- async function loadDocs() {
27
- allDocs = await fetchJSON('/api/documents') ?? []
28
- if (!$('#doc-tree')) return
29
- renderDocTree(allDocs)
30
- renderDocDataNote(allDocs)
31
- renderPrototypeGallery(allDocs)
32
- }
33
-
34
- app.replaceChildren(
35
- el('div', { className: 'page-toolbar' }, [
36
- search,
37
- refresh,
38
- copyIndex,
39
- downloadIndex,
40
- autoRefreshControl(loadDocs),
41
- el('div', { className: 'toolbar-spacer' }),
42
- el('span', { className: 'text-muted text-sm', id: 'doc-count' }),
43
- ]),
44
- el('div', { id: 'doc-data-note' }),
45
- panel(t('documents.prototypeGallery'), 'doc-prototypes'),
46
- el('div', { className: 'doc-layout' }, [
47
- el('div', { className: 'doc-tree', id: 'doc-tree' }, [
48
- el('div', { className: 'loading-placeholder', text: t('common.loading'), style: { height: '200px' } }),
49
- ]),
50
- el('div', { className: 'doc-renderer', id: 'doc-renderer' }, [
51
- emptyStateWithHint(t('documents.selectHint'), `${t('documents.supportedTypes')}. ${t('documents.prototypeHint')}`, '\uD83D\uDCC4'),
52
- ]),
53
- ])
54
- )
55
-
56
- await loadDocs()
57
-
58
- search.addEventListener('input', (event) => {
59
- const query = event.target.value.toLowerCase()
60
- const docs = query
61
- ? allDocs.filter(doc => doc.name.toLowerCase().includes(query) || doc.path.toLowerCase().includes(query))
62
- : allDocs
63
- renderDocTree(docs)
64
- })
65
-
66
- refresh.addEventListener('click', loadDocs)
67
- copyIndex.addEventListener('click', () => copyText(documentIndexText(allDocs), copyIndex))
68
- downloadIndex.addEventListener('click', () => {
69
- downloadText(`scale-documents-${Date.now()}.json`, JSON.stringify(documentIndexPayload(allDocs), null, 2), 'application/json;charset=utf-8')
70
- })
71
- }
72
-
73
- function renderDocDataNote(docs) {
74
- const node = $('#doc-data-note')
75
- if (!node) return
76
- const htmlCount = docs.filter(doc => doc.type === 'html').length
77
- node.replaceChildren(dataNote([
78
- { strong: true, text: t('common.snapshot') },
79
- `${t('common.lastLoaded')}: ${formatTime(Date.now())}`,
80
- t('documents.dataHint'),
81
- t('documents.previewable', { count: htmlCount }),
82
- ]))
83
- }
84
-
85
- function renderPrototypeGallery(docs) {
86
- const container = $('#doc-prototypes')
87
- if (!container) return
88
- const prototypes = docs.filter(doc => doc.type === 'html')
89
- if (prototypes.length === 0) {
90
- container.replaceChildren(emptyStateWithHint(t('documents.noPrototypes'), t('documents.noPrototypesHint'), '\u25cc'))
91
- return
92
- }
93
- container.replaceChildren(el('div', { className: 'prototype-grid' }, prototypes.map(doc => {
94
- const preview = el('button', { className: 'topo-btn', text: t('documents.preview') })
95
- preview.addEventListener('click', () => selectDocument(doc.path, doc.type))
96
- const open = el('button', { className: 'topo-btn', text: t('common.newTab') })
97
- open.addEventListener('click', () => window.open(documentUrl(doc.path), '_blank'))
98
- const copy = el('button', { className: 'topo-btn', text: t('documents.copyLink') })
99
- copy.addEventListener('click', () => copyText(new URL(documentUrl(doc.path), window.location.origin).href, copy))
100
- return el('div', { className: 'prototype-card' }, [
101
- el('div', { className: 'prototype-preview' }, [
102
- el('iframe', { attrs: { src: documentUrl(doc.path), title: doc.name } }),
103
- ]),
104
- el('div', { className: 'prototype-body' }, [
105
- el('div', { className: 'prototype-title', text: doc.name, title: doc.path }),
106
- el('div', { className: 'prototype-meta', text: `${doc.path} · ${formatSize(doc.size ?? 0)}` }),
107
- el('div', { className: 'action-row' }, [preview, open, copy]),
108
- ]),
109
- ])
110
- })))
111
- }
112
-
113
- function renderDocTree(docs) {
114
- const tree = $('#doc-tree')
115
- if (!tree) return
116
- const count = $('#doc-count')
117
- if (count) count.textContent = t('documents.docCount', { count: docs.length })
118
-
119
- if (docs.length === 0) {
120
- tree.replaceChildren(el('div', {
121
- className: 'text-muted text-sm',
122
- text: t('documents.noDocuments'),
123
- style: { padding: '12px' },
124
- }))
125
- return
126
- }
127
-
128
- const children = []
129
- const favDocs = docs.filter(doc => favorites.has(doc.path))
130
- if (favDocs.length > 0) {
131
- children.push(el('div', { className: 'doc-tree-folder', text: `\u2605 ${t('documents.favorites')}` }))
132
- children.push(...favDocs.map(file => renderDocItem(file)))
133
- }
134
-
135
- for (const [folder, files] of sortedFolders(docs)) {
136
- children.push(el('div', { className: 'doc-tree-folder', text: folder === 'root' ? '/' : folder }))
137
- children.push(...sortDocs(files).map(file => renderDocItem(file)))
138
- }
139
-
140
- tree.replaceChildren(...children)
141
- wireTree(tree, docs)
142
- }
143
-
144
- function sortedFolders(docs) {
145
- const folders = {}
146
- for (const doc of docs) {
147
- const parts = doc.path.split('/')
148
- const folder = parts.length > 1 ? parts.slice(0, -1).join('/') : 'root'
149
- if (!folders[folder]) folders[folder] = []
150
- folders[folder].push(doc)
151
- }
152
- return Object.entries(folders).sort(([left], [right]) => {
153
- const leftHasFav = folders[left].some(doc => favorites.has(doc.path))
154
- const rightHasFav = folders[right].some(doc => favorites.has(doc.path))
155
- if (leftHasFav && !rightHasFav) return -1
156
- if (!leftHasFav && rightHasFav) return 1
157
- return left.localeCompare(right)
158
- })
159
- }
160
-
161
- function sortDocs(files) {
162
- return [...files].sort((left, right) => {
163
- const leftFav = favorites.has(left.path) ? 0 : 1
164
- const rightFav = favorites.has(right.path) ? 0 : 1
165
- return leftFav - rightFav || left.name.localeCompare(right.name)
166
- })
167
- }
168
-
169
- function renderDocItem(file) {
170
- const isFav = favorites.has(file.path)
171
- return el('div', {
172
- className: ['doc-tree-item', currentDoc === file.path ? 'active' : ''].filter(Boolean).join(' '),
173
- dataset: { path: file.path, type: file.type },
174
- style: { display: 'flex', alignItems: 'center', gap: '6px' },
175
- }, [
176
- el('span', { text: getDocIcon(file.type) }),
177
- el('span', {
178
- text: file.name,
179
- style: { flex: '1', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
180
- }),
181
- el('span', { className: 'text-muted text-sm', text: formatSize(file.size), style: { flexShrink: '0' } }),
182
- el('span', {
183
- className: 'doc-fav-btn',
184
- text: isFav ? '\u2605' : '\u2606',
185
- title: isFav ? t('documents.removeFromFavorites') : t('documents.addToFavorites'),
186
- dataset: { path: file.path },
187
- style: { cursor: 'pointer', color: isFav ? '#ffaa00' : 'var(--text-2)', fontSize: '12px' },
188
- }),
189
- ])
190
- }
191
-
192
- function wireTree(tree, docs) {
193
- $$('.doc-tree-item', tree).forEach((item) => {
194
- item.addEventListener('click', (event) => {
195
- if (event.target.classList.contains('doc-fav-btn')) return
196
- selectDocument(item.dataset.path, item.dataset.type)
197
- })
198
- })
199
- $$('.doc-fav-btn', tree).forEach((button) => {
200
- button.addEventListener('click', (event) => {
201
- event.stopPropagation()
202
- const path = button.dataset.path
203
- if (favorites.has(path)) favorites.delete(path)
204
- else favorites.add(path)
205
- localStorage.setItem('scale-doc-favorites', JSON.stringify([...favorites]))
206
- renderDocTree(docs)
207
- })
208
- })
209
- }
210
-
211
- function selectDocument(path, type) {
212
- const tree = $('#doc-tree')
213
- if (tree) {
214
- $$('.doc-tree-item', tree).forEach(node => node.classList.toggle('active', node.dataset.path === path))
215
- }
216
- loadDocument(path, type)
217
- }
218
-
219
- function getDocIcon(type) {
220
- switch (type) {
221
- case 'html': return '\uD83C\uDF10'
222
- case 'json': return '\uD83D\uDCC4'
223
- case 'md': return '\uD83D\uDCDD'
224
- default: return '\uD83D\uDCC4'
225
- }
226
- }
227
-
228
- async function loadDocument(path, type) {
229
- const renderer = $('#doc-renderer')
230
- if (!renderer) return
231
- renderer.replaceChildren(el('div', { className: 'loading-placeholder', text: t('common.loading') }))
232
-
233
- try {
234
- const res = await fetch(documentUrl(path))
235
- if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
236
- const text = await res.text()
237
- if (!renderer.isConnected || $('#doc-renderer') !== renderer) return
238
-
239
- if (type === 'html') renderHtmlDocument(renderer, path, text)
240
- else if (type === 'md') renderMarkdownDocument(renderer, path, text)
241
- else if (type === 'json') renderJsonDocument(renderer, path, text)
242
- else renderPlainDocument(renderer, path, text)
243
-
244
- currentDoc = path
245
- } catch (error) {
246
- renderer.replaceChildren(emptyState(`${t('documents.failedToLoad')}: ${errorMessage(error)}`, '\u26A0'))
247
- }
248
- }
249
-
250
- function renderHtmlDocument(renderer, path, text) {
251
- const openButton = el('button', {
252
- className: 'topo-btn doc-open-ext',
253
- text: `\u2197 ${t('common.newTab')}`,
254
- title: t('common.newTab'),
255
- })
256
- openButton.addEventListener('click', () => window.open(documentUrl(path), '_blank'))
257
- renderer.replaceChildren(
258
- docHeader(path, formatSize(text.length), documentActions(path, text, [openButton])),
259
- el('iframe', {
260
- attrs: { src: documentUrl(path) },
261
- style: {
262
- width: '100%',
263
- height: 'calc(100% - 50px)',
264
- minHeight: '550px',
265
- border: '1px solid var(--border)',
266
- borderRadius: 'var(--radius)',
267
- },
268
- })
269
- )
270
- }
271
-
272
- function renderMarkdownDocument(renderer, path, text) {
273
- renderer.replaceChildren(
274
- docHeader(path, formatSize(text.length), documentActions(path, text)),
275
- renderMarkdown(text)
276
- )
277
- }
278
-
279
- function renderJsonDocument(renderer, path, text) {
280
- try {
281
- const json = JSON.parse(text)
282
- const formatted = JSON.stringify(json, null, 2)
283
- renderer.replaceChildren(
284
- docHeader(path, `${Object.keys(json).length} ${t('documents.keys')} \u00b7 ${formatSize(text.length)}`, documentActions(path, formatted)),
285
- renderJsonPre(formatted)
286
- )
287
- } catch (error) {
288
- observeRecoverableError(error)
289
- renderPlainDocument(renderer, path, text)
290
- }
291
- }
292
-
293
- function renderPlainDocument(renderer, path, text) {
294
- renderer.replaceChildren(
295
- docHeader(path, formatSize(text.length), documentActions(path, text)),
296
- el('pre', {
297
- text,
298
- style: { fontSize: '13px', whiteSpace: 'pre-wrap', color: 'var(--text-1)', lineHeight: '1.5' },
299
- })
300
- )
301
- }
302
-
303
- function docHeader(path, meta, actions = []) {
304
- return el('div', {
305
- style: { marginBottom: '12px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
306
- }, [
307
- el('div', {}, [
308
- el('span', { text: fileName(path), style: { fontWeight: '600' } }),
309
- el('span', { className: 'text-muted text-sm', text: meta, style: { marginLeft: '8px' } }),
310
- ]),
311
- actions.length ? el('div', { style: { display: 'flex', gap: '8px' } }, actions) : null,
312
- ])
313
- }
314
-
315
- function documentActions(path, text, extraActions = []) {
316
- const copyButton = el('button', { className: 'topo-btn doc-copy', text: t('common.copy'), title: t('common.copy') })
317
- copyButton.addEventListener('click', async () => {
318
- try {
319
- await copyText(text, copyButton)
320
- } catch (error) {
321
- observeRecoverableError(error)
322
- }
323
- })
324
- const downloadButton = el('button', { className: 'topo-btn doc-download', text: t('common.download'), title: t('common.download') })
325
- downloadButton.addEventListener('click', () => downloadText(fileName(path), text, contentTypeForPath(path)))
326
- return [copyButton, downloadButton, ...extraActions]
327
- }
328
-
329
- function documentIndexPayload(docs) {
330
- return {
331
- exportedAt: new Date().toISOString(),
332
- count: docs.length,
333
- previewableHtml: docs.filter(doc => doc.type === 'html').length,
334
- documents: docs.map(doc => ({
335
- name: doc.name,
336
- path: doc.path,
337
- type: doc.type,
338
- size: doc.size,
339
- })),
340
- }
341
- }
342
-
343
- function documentIndexText(docs) {
344
- return documentIndexPayload(docs).documents
345
- .map(doc => `${doc.type}\t${formatSize(doc.size ?? 0)}\t${doc.path}`)
346
- .join('\n')
347
- }
348
-
349
- function contentTypeForPath(path) {
350
- const lower = String(path).toLowerCase()
351
- if (lower.endsWith('.json')) return 'application/json;charset=utf-8'
352
- if (lower.endsWith('.html')) return 'text/html;charset=utf-8'
353
- if (lower.endsWith('.md')) return 'text/markdown;charset=utf-8'
354
- return 'text/plain;charset=utf-8'
355
- }
356
-
357
- function renderMarkdown(markdown) {
358
- const root = el('div', { className: 'markdown-body', style: { fontSize: '14px', lineHeight: '1.7', color: 'var(--text-0)' } })
359
- const lines = markdown.replace(/\r\n/g, '\n').split('\n')
360
- let codeLines = []
361
- let codeLang = ''
362
- let paragraph = []
363
- let list = null
364
-
365
- const flushParagraph = () => {
366
- if (paragraph.length === 0) return
367
- const p = el('p', { style: { margin: '8px 0' } })
368
- appendInline(p, paragraph.join(' '))
369
- root.appendChild(p)
370
- paragraph = []
371
- }
372
- const flushList = () => {
373
- if (!list) return
374
- root.appendChild(list)
375
- list = null
376
- }
377
- const flushCode = () => {
378
- const code = el('code', { text: codeLines.join('\n') })
379
- const pre = el('pre', {
380
- attrs: codeLang ? { 'data-lang': codeLang } : {},
381
- style: { background: 'var(--bg-2)', padding: '12px', borderRadius: 'var(--radius)', fontSize: '13px', lineHeight: '1.5', overflow: 'auto', margin: '12px 0' },
382
- }, [code])
383
- root.appendChild(pre)
384
- codeLines = []
385
- codeLang = ''
386
- }
387
-
388
- for (const line of lines) {
389
- const fence = line.match(/^```(\w*)\s*$/)
390
- if (fence && codeLang === '' && codeLines.length === 0) {
391
- flushParagraph()
392
- flushList()
393
- codeLang = fence[1] || 'plain'
394
- codeLines = ['__OPEN__']
395
- continue
396
- }
397
- if (fence && codeLines.length > 0) {
398
- codeLines.shift()
399
- flushCode()
400
- continue
401
- }
402
- if (codeLines.length > 0) {
403
- codeLines.push(line)
404
- continue
405
- }
406
-
407
- if (!line.trim()) {
408
- flushParagraph()
409
- flushList()
410
- continue
411
- }
412
- const heading = line.match(/^(#{1,3})\s+(.+)$/)
413
- if (heading) {
414
- flushParagraph()
415
- flushList()
416
- root.appendChild(headingNode(heading[1].length, heading[2]))
417
- continue
418
- }
419
- if (/^---+$/.test(line.trim())) {
420
- flushParagraph()
421
- flushList()
422
- root.appendChild(el('hr', { style: { border: 'none', borderTop: '1px solid var(--border)', margin: '20px 0' } }))
423
- continue
424
- }
425
- const item = line.match(/^[-*]\s+(.+)$/)
426
- if (item) {
427
- flushParagraph()
428
- if (!list) list = el('ul', { style: { margin: '8px 0 8px 20px' } })
429
- const li = el('li', { style: { padding: '2px 0' } })
430
- appendInline(li, item[1])
431
- list.appendChild(li)
432
- continue
433
- }
434
- paragraph.push(line.trim())
435
- }
436
-
437
- if (codeLines.length > 0) {
438
- codeLines.shift()
439
- flushCode()
440
- }
441
- flushParagraph()
442
- flushList()
443
- return root
444
- }
445
-
446
- function headingNode(level, text) {
447
- const sizes = { 1: '22px', 2: '18px', 3: '16px' }
448
- const margins = { 1: '28px 0 12px', 2: '24px 0 10px', 3: '20px 0 8px' }
449
- const node = el(`h${level}`, { style: { margin: margins[level], fontSize: sizes[level], color: 'var(--text-0)' } })
450
- appendInline(node, text)
451
- return node
452
- }
453
-
454
- function appendInline(parent, text) {
455
- const pattern = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\)|\*[^*]+\*)/g
456
- let lastIndex = 0
457
- for (const match of text.matchAll(pattern)) {
458
- if (match.index > lastIndex) parent.appendChild(document.createTextNode(text.slice(lastIndex, match.index)))
459
- parent.appendChild(inlineNode(match[0]))
460
- lastIndex = match.index + match[0].length
461
- }
462
- if (lastIndex < text.length) parent.appendChild(document.createTextNode(text.slice(lastIndex)))
463
- }
464
-
465
- function inlineNode(token) {
466
- if (token.startsWith('**')) return el('strong', { text: token.slice(2, -2) })
467
- if (token.startsWith('`')) return el('code', { text: token.slice(1, -1), style: { background: 'var(--bg-2)', padding: '2px 6px', borderRadius: '3px', fontSize: '12px' } })
468
- if (token.startsWith('*')) return el('em', { text: token.slice(1, -1) })
469
- const link = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/)
470
- if (!link) return document.createTextNode(token)
471
- return el('a', { text: link[1], attrs: { href: safeHref(link[2]), target: '_blank', rel: 'noopener noreferrer' } })
472
- }
473
-
474
- function renderJsonPre(formatted) {
475
- const pre = el('pre', {
476
- style: { fontSize: '13px', lineHeight: '1.5', whiteSpace: 'pre-wrap', color: 'var(--text-1)', background: 'var(--bg-2)', padding: '16px', borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 'calc(100% - 50px)' },
477
- })
478
- const tokenPattern = /"(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b|\btrue\b|\bfalse\b|\bnull\b/gi
479
- let lastIndex = 0
480
- for (const match of formatted.matchAll(tokenPattern)) {
481
- if (match.index > lastIndex) pre.appendChild(document.createTextNode(formatted.slice(lastIndex, match.index)))
482
- pre.appendChild(jsonToken(match[0], formatted.slice(match.index + match[0].length)))
483
- lastIndex = match.index + match[0].length
484
- }
485
- if (lastIndex < formatted.length) pre.appendChild(document.createTextNode(formatted.slice(lastIndex)))
486
- return pre
487
- }
488
-
489
- function jsonToken(token, tail) {
490
- const color = token.startsWith('"')
491
- ? (tail.trimStart().startsWith(':') ? '#5588ff' : '#00dc82')
492
- : (/^(true|false|null)$/i.test(token) ? '#aa88ff' : '#ffaa00')
493
- return el('span', { text: token, style: { color } })
494
- }
495
-
496
- function emptyStateWithHint(message, hint, icon) {
497
- const state = emptyState(message, icon)
498
- state.appendChild(el('p', { className: 'text-muted text-sm', text: hint, style: { marginTop: '8px' } }))
499
- return state
500
- }
501
-
502
- function documentUrl(path) {
503
- return `/api/documents/${String(path).split('/').map(segment => encodeURIComponent(segment)).join('/')}`
504
- }
505
-
506
- function safeHref(value) {
507
- const href = String(value || '')
508
- if (/^(https?:|mailto:|#|\/)/i.test(href)) return href
509
- return '#'
510
- }
511
-
512
- function fileName(path) {
513
- return String(path).split('/').pop() || path
514
- }
515
-
516
- function formatSize(bytes) {
517
- if (bytes < 1024) return bytes + 'B'
518
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB'
519
- return (bytes / (1024 * 1024)).toFixed(1) + 'MB'
520
- }
521
-
522
- function errorMessage(error) {
523
- return error instanceof Error ? error.message : String(error || 'Unknown error')
524
- }
525
-
526
- function observeRecoverableError(error) {
527
- void error
528
- }
529
-
530
- window.addEventListener('search', (event) => {
531
- const query = event.detail.toLowerCase()
532
- $$('.doc-tree-item').forEach((item) => {
533
- const name = item.textContent.toLowerCase()
534
- item.style.display = name.includes(query) ? '' : 'none'
535
- })
536
- })
537
-
538
- window.DashboardPages = window.DashboardPages || {}
539
- window.DashboardPages.documents = renderDocuments
540
- })()