{"uuid": "f80e3d72-956d-4145-8cf2-a400e77705d2", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-1234", "type": "seen", "source": "https://gist.github.com/Eimo-Bai/432f0d8dba6256bae3b930643076345e", "content": "\n\n\n    \n    \n    Trace Viewer\n    \n        :root {\n            --bg-primary: #0b0f19;\n            --bg-secondary: #111827;\n            --bg-tertiary: #1f2937;\n            --bg-hover: #374151;\n            --border-color: #374151;\n            --text-primary: #f9fafb;\n            --text-secondary: #9ca3af;\n            --text-muted: #6b7280;\n            --accent-success: #10b981;\n            --accent-failed: #ef4444;\n            --accent-warning: #f59e0b;\n            --accent-info: #3b82f6;\n            --accent-purple: #8b5cf6;\n            --font-mono: \"Menlo\", \"Monaco\", \"Courier New\", monospace;\n            --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);\n        }\n\n        * {\n            box-sizing: border-box;\n            margin: 0;\n            padding: 0;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            height: 100vh;\n            overflow: hidden;\n            font-size: 13px;\n            line-height: 1.5;\n        }\n\n        /* Layout */\n        .container {\n            display: flex;\n            height: 100vh;\n        }\n\n        /* Sidebar */\n        .sidebar {\n            width: 320px;\n            background-color: var(--bg-secondary);\n            border-right: 1px solid var(--border-color);\n            display: flex;\n            flex-direction: column;\n            flex-shrink: 0;\n        }\n\n        .sidebar-header {\n            padding: 16px;\n            border-bottom: 1px solid var(--border-color);\n            font-weight: 600;\n            font-size: 14px;\n            color: var(--text-secondary);\n            text-transform: uppercase;\n            letter-spacing: 0.05em;\n        }\n\n        .session-list {\n            overflow-y: auto;\n            flex: 1;\n        }\n\n        .session-item {\n            padding: 12px 16px;\n            border-bottom: 1px solid var(--border-color);\n            cursor: pointer;\n            transition: all 0.15s;\n            position: relative;\n        }\n\n        .session-item:hover {\n            background-color: var(--bg-hover);\n        }\n\n        .session-item.active {\n            background-color: rgba(59, 130, 246, 0.1);\n            border-left: 3px solid var(--accent-info);\n        }\n\n        .session-item.has-failure {\n            border-left: 3px solid var(--accent-failed);\n        }\n\n        .session-item.has-failure.active {\n            border-left: 3px solid var(--accent-failed);\n            background-color: rgba(239, 68, 68, 0.1);\n        }\n\n        .session-name {\n            font-weight: 500;\n            margin-bottom: 4px;\n            color: var(--text-primary);\n            white-space: nowrap;\n            overflow: hidden;\n            text-overflow: ellipsis;\n        }\n\n        .session-meta {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            font-size: 12px;\n            color: var(--text-secondary);\n        }\n\n        .session-status {\n            display: inline-flex;\n            align-items: center;\n            gap: 4px;\n        }\n\n        .status-badge {\n            display: inline-flex;\n            align-items: center;\n            padding: 2px 6px;\n            border-radius: 4px;\n            font-size: 11px;\n            font-weight: 600;\n            text-transform: uppercase;\n        }\n\n        .status-success { \n            background: rgba(16, 185, 129, 0.2); \n            color: var(--accent-success); \n        }\n        \n        .status-failed { \n            background: rgba(239, 68, 68, 0.2); \n            color: var(--accent-failed); \n        }\n\n        .status-partial {\n            background: rgba(245, 158, 11, 0.2);\n            color: var(--accent-warning);\n        }\n\n        .warning-icon {\n            color: var(--accent-failed);\n            margin-left: 4px;\n        }\n\n        /* Main Content */\n        .main {\n            flex: 1;\n            display: flex;\n            flex-direction: column;\n            overflow: hidden;\n            background-color: var(--bg-primary);\n        }\n\n        .main-header {\n            padding: 20px 24px;\n            border-bottom: 1px solid var(--border-color);\n            background-color: var(--bg-secondary);\n        }\n\n        .header-title {\n            font-size: 18px;\n            font-weight: 600;\n            margin-bottom: 8px;\n            display: flex;\n            align-items: center;\n            gap: 12px;\n        }\n\n        .header-meta {\n            display: flex;\n            gap: 24px;\n            color: var(--text-secondary);\n            font-size: 13px;\n        }\n\n        .meta-item {\n            display: flex;\n            align-items: center;\n            gap: 6px;\n        }\n\n        .trace-container {\n            flex: 1;\n            overflow-y: auto;\n            padding: 24px;\n        }\n\n        /* Storyline */\n        .storyline {\n            margin-bottom: 16px;\n            background-color: var(--bg-secondary);\n            border: 1px solid var(--border-color);\n            border-radius: 8px;\n            overflow: hidden;\n        }\n\n        .storyline-header {\n            padding: 12px 16px;\n            background-color: rgba(139, 92, 246, 0.1);\n            border-bottom: 1px solid var(--border-color);\n            cursor: pointer;\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            user-select: none;\n        }\n\n        .storyline-header:hover {\n            background-color: rgba(139, 92, 246, 0.15);\n        }\n\n        .storyline-title {\n            display: flex;\n            align-items: center;\n            gap: 8px;\n            font-weight: 600;\n            color: var(--accent-purple);\n        }\n\n        .storyline-meta {\n            color: var(--text-muted);\n            font-size: 12px;\n        }\n\n        .storyline-content {\n            padding: 8px 0;\n        }\n\n        .storyline-content.collapsed {\n            display: none;\n        }\n\n        /* Span Tree */\n        .span-list {\n            position: relative;\n        }\n\n        .span-item {\n            position: relative;\n        }\n\n        .span-row {\n            display: flex;\n            align-items: center;\n            padding: 10px 16px;\n            cursor: pointer;\n            border-bottom: 1px solid rgba(55, 65, 81, 0.5);\n            transition: background-color 0.15s;\n            position: relative;\n        }\n\n        .span-row:hover {\n            background-color: var(--bg-hover);\n        }\n\n        .span-row.failed {\n            background-color: rgba(239, 68, 68, 0.05);\n        }\n\n        .span-row.failed:hover {\n            background-color: rgba(239, 68, 68, 0.1);\n        }\n\n        .span-row.failed::before {\n            content: '';\n            position: absolute;\n            left: 0;\n            top: 0;\n            bottom: 0;\n            width: 3px;\n            background-color: var(--accent-failed);\n        }\n\n        /* Tree Lines */\n        .span-children {\n            position: relative;\n            margin-left: 20px;\n        }\n\n        .span-children::before {\n            content: '';\n            position: absolute;\n            left: 0;\n            top: 0;\n            bottom: 0;\n            width: 1px;\n            background-color: var(--border-color);\n        }\n\n        .span-indent {\n            display: flex;\n            align-items: center;\n            margin-right: 8px;\n            color: var(--text-muted);\n        }\n\n        .toggle-btn {\n            width: 16px;\n            height: 16px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            cursor: pointer;\n            border-radius: 3px;\n            margin-right: 4px;\n            font-size: 10px;\n            color: var(--text-secondary);\n        }\n\n        .toggle-btn:hover {\n            background-color: var(--bg-hover);\n            color: var(--text-primary);\n        }\n\n        .span-content {\n            flex: 1;\n            display: flex;\n            align-items: center;\n            gap: 12px;\n            min-width: 0;\n        }\n\n        .span-name {\n            font-weight: 500;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n            min-width: 0;\n        }\n\n        .span-tool {\n            font-family: var(--font-mono);\n            font-size: 11px;\n            color: var(--text-muted);\n            background-color: var(--bg-tertiary);\n            padding: 2px 6px;\n            border-radius: 4px;\n            white-space: nowrap;\n        }\n\n        .span-status-icon {\n            width: 16px;\n            height: 16px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            border-radius: 50%;\n            font-size: 10px;\n        }\n\n        .span-status-icon.success {\n            color: var(--accent-success);\n        }\n\n        .span-status-icon.failed {\n            background-color: var(--accent-failed);\n            color: white;\n        }\n\n        /* Duration Bar */\n        .span-timing {\n            display: flex;\n            align-items: center;\n            gap: 12px;\n            width: 200px;\n            flex-shrink: 0;\n        }\n\n        .duration-bar-container {\n            flex: 1;\n            height: 4px;\n            background-color: var(--bg-tertiary);\n            border-radius: 2px;\n            overflow: hidden;\n            position: relative;\n        }\n\n        .duration-bar {\n            height: 100%;\n            border-radius: 2px;\n            transition: width 0.3s ease;\n        }\n\n        .duration-bar.fast { background-color: var(--accent-success); }\n        .duration-bar.medium { background-color: var(--accent-info); }\n        .duration-bar.slow { background-color: var(--accent-warning); }\n        .duration-bar.critical { background-color: var(--accent-failed); }\n\n        .duration-text {\n            font-family: var(--font-mono);\n            font-size: 11px;\n            color: var(--text-secondary);\n            width: 60px;\n            text-align: right;\n            white-space: nowrap;\n        }\n\n        /* Span Detail */\n        .span-detail {\n            background-color: var(--bg-primary);\n            border-top: 1px solid var(--border-color);\n            padding: 16px;\n            display: none;\n        }\n\n        .span-detail.expanded {\n            display: block;\n        }\n\n        .detail-section {\n            margin-bottom: 16px;\n        }\n\n        .detail-section:last-child {\n            margin-bottom: 0;\n        }\n\n        .detail-label {\n            font-size: 11px;\n            font-weight: 600;\n            color: var(--text-secondary);\n            text-transform: uppercase;\n            letter-spacing: 0.05em;\n            margin-bottom: 8px;\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n        }\n\n        .detail-content {\n            background-color: var(--bg-secondary);\n            border: 1px solid var(--border-color);\n            border-radius: 6px;\n            padding: 12px;\n            font-family: var(--font-mono);\n            font-size: 12px;\n            max-height: 400px;\n            overflow: auto;\n            white-space: pre-wrap;\n            word-break: break-word;\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        .detail-content.empty {\n            color: var(--text-muted);\n            font-style: italic;\n        }\n\n        .size-hint {\n            font-size: 10px;\n            color: var(--text-muted);\n            font-weight: normal;\n            text-transform: none;\n        }\n\n        /* Scrollbar */\n        ::-webkit-scrollbar {\n            width: 8px;\n            height: 8px;\n        }\n\n        ::-webkit-scrollbar-track {\n            background: var(--bg-secondary);\n        }\n\n        ::-webkit-scrollbar-thumb {\n            background: var(--bg-tertiary);\n            border-radius: 4px;\n        }\n\n        ::-webkit-scrollbar-thumb:hover {\n            background: var(--bg-hover);\n        }\n\n        /* Icons */\n        .icon {\n            display: inline-block;\n            width: 1em;\n            height: 1em;\n            text-align: center;\n        }\n\n        .chevron {\n            transition: transform 0.2s;\n        }\n\n        .chevron.collapsed {\n            transform: rotate(-90deg);\n        }\n\n        /* Empty State */\n        .empty-state {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            height: 100%;\n            color: var(--text-muted);\n        }\n\n        .empty-state-title {\n            font-size: 18px;\n            margin-bottom: 8px;\n            color: var(--text-secondary);\n        }\n\n        /* Timestamp */\n        .timestamp {\n            font-family: var(--font-mono);\n            font-size: 11px;\n            color: var(--text-muted);\n        }\n\n        /* Highlight */\n        .highlight {\n            background-color: rgba(245, 158, 11, 0.3);\n            padding: 0 2px;\n            border-radius: 2px;\n        }\n    \n\n\n    \n\n        \n\n            \nSessions\n            \n\n        \n        \n        \n\n            \n\n                \n\n                    \n\u9009\u62e9 Session \u67e5\u770b\u8be6\u60c5\n                    \n\u4ece\u5de6\u4fa7\u5217\u8868\u9009\u62e9\u4e00\u4e2a\u8ffd\u8e2a\u4f1a\u8bdd\n                \n            \n            \n\n        \n    \n\n    \n        // Mock Data Generator\n        const generateMockData = () =&gt; {\n            const largeOutput = JSON.stringify({\n                results: Array(50).fill(0).map((_, i) =&gt; ({\n                    id: i,\n                    file: `src/components/Component${i}.tsx`,\n                    matches: [\n                        { line: 10 + i, text: \"// TODO: optimize this function\" },\n                        { line: 25 + i, text: \"// TODO: add error handling\" }\n                    ],\n                    metadata: {\n                        complexity: Math.floor(Math.random() * 10),\n                        last_modified: \"2026-06-08T10:23:01Z\"\n                    }\n                })),\n                summary: { total: 50, todo_count: 100, files_scanned: 150 }\n            }, null, 2);\n\n            return {\n                sessions: [\n                    {\n                        session_id: \"sess-001\",\n                        name: \"\u7528\u6237\u95ee\uff1a\u628a\u9879\u76ee\u91cc\u7684 TODO \u90fd\u6c47\u603b\u6210 markdown\",\n                        start_ts: \"2026-06-08T10:23:01.120Z\",\n                        duration_ms: 18432,\n                        status: \"success\",\n                        storylines: [\n                            {\n                                storyline_id: \"sl-001-a\",\n                                name: \"\u626b\u63cf\u9879\u76ee\u7ed3\u6784\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-001\",\n                                        parent_id: null,\n                                        name: \"list_files\",\n                                        tool: \"fs.list\",\n                                        start_ts: \"2026-06-08T10:23:01.150Z\",\n                                        duration_ms: 120,\n                                        status: \"success\",\n                                        input: { path: \".\", recursive: true },\n                                        output: \"src/ tests/ README.md package.json ...\"\n                                    },\n                                    {\n                                        span_id: \"sp-002\",\n                                        parent_id: null,\n                                        name: \"search_todos\",\n                                        tool: \"grep.search\",\n                                        start_ts: \"2026-06-08T10:23:01.300Z\",\n                                        duration_ms: 3200,\n                                        status: \"success\",\n                                        input: { pattern: \"TODO|FIXME|XXX\", path: \"src/\" },\n                                        output: largeOutput // &gt; 2KB\n                                    }\n                                ]\n                            },\n                            {\n                                storyline_id: \"sl-001-b\",\n                                name: \"\u751f\u6210 Markdown \u62a5\u544a\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-003\",\n                                        parent_id: null,\n                                        name: \"analyze_results\",\n                                        tool: \"analyzer\",\n                                        start_ts: \"2026-06-08T10:23:05.000Z\",\n                                        duration_ms: 450,\n                                        status: \"success\",\n                                        input: { data: \"...\" },\n                                        output: { summary: \"Found 50 TODOs\" }\n                                    },\n                                    {\n                                        span_id: \"sp-004\",\n                                        parent_id: null,\n                                        name: \"generate_markdown\",\n                                        tool: \"template.render\",\n                                        start_ts: \"2026-06-08T10:23:05.500Z\",\n                                        duration_ms: 180,\n                                        status: \"failed\",\n                                        input: { template: \"todo_report.md\" },\n                                        output: \"Error: Template not found\"\n                                    }\n                                ]\n                            },\n                            {\n                                storyline_id: \"sl-001-c\",\n                                name: \"\u5e76\u884c\u5904\u7406\u5927\u6587\u4ef6\uff0815\u4e2a\u540c\u7ea7 Span\uff09\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-005\",\n                                        parent_id: null,\n                                        name: \"batch_processor\",\n                                        tool: \"batch\",\n                                        start_ts: \"2026-06-08T10:23:06.000Z\",\n                                        duration_ms: 8000,\n                                        status: \"success\",\n                                        input: { files: Array(15).fill(0).map((_, i) =&gt; `file_${i}.txt`) },\n                                        output: \"Processing 15 files in parallel\",\n                                        children_ids: [\"sp-006\", \"sp-007\", \"sp-008\", \"sp-009\", \"sp-010\", \"sp-011\", \"sp-012\", \"sp-013\", \"sp-014\", \"sp-015\", \"sp-016\", \"sp-017\", \"sp-018\", \"sp-019\", \"sp-020\"]\n                                    },\n                                    ...Array(15).fill(0).map((_, i) =&gt; ({\n                                        span_id: `sp-${String(i + 6).padStart(3, '0')}`,\n                                        parent_id: \"sp-005\",\n                                        name: `process_file_${i}.txt`,\n                                        tool: \"file.process\",\n                                        start_ts: `2026-06-08T10:23:06.${100 + i * 50}Z`,\n                                        duration_ms: 500 + Math.random() * 3000,\n                                        status: i === 7 ? \"failed\" : \"success\", // \u7b2c8\u4e2a\u5931\u8d25\n                                        input: { filename: `file_${i}.txt` },\n                                        output: i === 7 ? \"Permission denied\" : `Processed ${i} items`\n                                    }))\n                                ]\n                            }\n                        ]\n                    },\n                    {\n                        session_id: \"sess-002\",\n                        name: \"\u91cd\u6784\u6570\u636e\u5e93\u8fde\u63a5\u6c60\u914d\u7f6e\",\n                        start_ts: \"2026-06-08T11:15:30.000Z\",\n                        duration_ms: 5432,\n                        status: \"success\",\n                        storylines: [\n                            {\n                                storyline_id: \"sl-002-a\",\n                                name: \"\u5206\u6790\u5f53\u524d\u914d\u7f6e\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-101\",\n                                        parent_id: null,\n                                        name: \"read_config\",\n                                        tool: \"config.read\",\n                                        start_ts: \"2026-06-08T11:15:30.100Z\",\n                                        duration_ms: 45,\n                                        status: \"success\",\n                                        input: { file: \"database.yml\" },\n                                        output: \"pool_size: 10\\nmax_overflow: 20\"\n                                    },\n                                    {\n                                        span_id: \"sp-102\",\n                                        parent_id: null,\n                                        name: \"validate_config\",\n                                        tool: \"config.validate\",\n                                        start_ts: \"2026-06-08T11:15:30.200Z\",\n                                        duration_ms: 120,\n                                        status: \"success\",\n                                        input: { schema: \"db_schema.json\" },\n                                        output: \"Valid\"\n                                    }\n                                ]\n                            },\n                            {\n                                storyline_id: \"sl-002-b\",\n                                name: \"\u6267\u884c\u91cd\u6784\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-103\",\n                                        parent_id: null,\n                                        name: \"update_pool\",\n                                        tool: \"db.update_pool\",\n                                        start_ts: \"2026-06-08T11:15:31.000Z\",\n                                        duration_ms: 5000,\n                                        status: \"success\",\n                                        input: { new_size: 20 },\n                                        output: \"Pool resized successfully\"\n                                    }\n                                ]\n                            }\n                        ]\n                    },\n                    {\n                        session_id: \"sess-003\",\n                        name: \"\u4fee\u590d\u7528\u6237\u8ba4\u8bc1\u6f0f\u6d1e\",\n                        start_ts: \"2026-06-08T09:00:00.000Z\",\n                        duration_ms: 3250,\n                        status: \"failed\",\n                        storylines: [\n                            {\n                                storyline_id: \"sl-003-a\",\n                                name: \"\u5b89\u5168\u626b\u63cf\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-201\",\n                                        parent_id: null,\n                                        name: \"security_scan\",\n                                        tool: \"security.scan\",\n                                        start_ts: \"2026-06-08T09:00:00.100Z\",\n                                        duration_ms: 3000,\n                                        status: \"failed\",\n                                        input: { target: \"auth_module\" },\n                                        output: \"CVE-2026-1234 detected: JWT validation bypass\"\n                                    }\n                                ]\n                            }\n                        ]\n                    },\n                    {\n                        session_id: \"sess-004\",\n                        name: \"\u4f18\u5316\u56fe\u7247\u8d44\u6e90\u52a0\u8f7d\",\n                        start_ts: \"2026-06-08T14:20:00.000Z\",\n                        duration_ms: 12500,\n                        status: \"success\",\n                        storylines: [\n                            {\n                                storyline_id: \"sl-004-a\",\n                                name: \"\u56fe\u7247\u538b\u7f29\",\n                                spans: [\n                                    {\n                                        span_id: \"sp-301\",\n                                        parent_id: null,\n                                        name: \"compress_images\",\n                                        tool: \"image.compress\",\n                                        start_ts: \"2026-06-08T14:20:00.500Z\",\n                                        duration_ms: 12000,\n                                        status: \"success\",\n                                        input: { quality: 85, format: \"webp\" },\n                                        output: \"Compressed 150 images, saved 45MB\"\n                                    }\n                                ]\n                            }\n                        ]\n                    }\n                ]\n            };\n        };\n\n        // Data Preparation\n        const rawData = generateMockData();\n        \n        // Process data to build trees and add metadata\n        const processedSessions = rawData.sessions.map(session =&gt; {\n            // Check for failures in spans\n            let hasFailedSpans = false;\n            let maxDuration = 0;\n\n            const processStoryline = (storyline) =&gt; {\n                const spanMap = new Map();\n                const roots = [];\n\n                // Build map\n                storyline.spans.forEach(span =&gt; {\n                    spanMap.set(span.span_id, { ...span, children: [] });\n                    if (span.duration_ms &gt; maxDuration) maxDuration = span.duration_ms;\n                });\n\n                // Build tree\n                storyline.spans.forEach(span =&gt; {\n                    const node = spanMap.get(span.span_id);\n                    if (span.parent_id &amp;&amp; spanMap.has(span.parent_id)) {\n                        spanMap.get(span.parent_id).children.push(node);\n                    } else {\n                        roots.push(node);\n                    }\n                    \n                    if (span.status === \"failed\") hasFailedSpans = true;\n                });\n\n                // Sort by timestamp\n                const sortByTime = (a, b) =&gt; new Date(a.start_ts) - new Date(b.start_ts);\n                roots.sort(sortByTime);\n                spanMap.forEach(node =&gt; node.children.sort(sortByTime));\n\n                return { ...storyline, spanRoots: roots, spanMap };\n            };\n\n            const storylines = session.storylines.map(processStoryline);\n\n            return {\n                ...session,\n                storylines,\n                maxDuration,\n                hasFailedSpans,\n                // Override status if success but has failures\n                displayStatus: session.status === \"success\" &amp;&amp; hasFailedSpans ? \"partial\" : session.status\n            };\n        });\n\n        // State Management\n        const state = {\n            currentSessionId: null,\n            expandedStorylines: new Map(), // sessionId -&gt; Set of storylineIds\n            expandedSpans: new Map(), // sessionId -&gt; Set of spanIds\n            expandedDetails: new Map() // sessionId -&gt; Set of spanIds with open details\n        };\n\n        // Initialize state for all sessions\n        processedSessions.forEach(s =&gt; {\n            state.expandedStorylines.set(s.session_id, new Set());\n            state.expandedSpans.set(s.session_id, new Set());\n            state.expandedDetails.set(s.session_id, new Set());\n            \n            // Expand first storyline by default\n            if (s.storylines.length &gt; 0) {\n                state.expandedStorylines.get(s.session_id).add(s.storylines[0].storyline_id);\n            }\n        });\n\n        // Helpers\n        const formatDuration = (ms) =&gt; {\n            if (ms &lt; 1000) return `${ms}ms`;\n            if (ms &lt; 60000) return `${(ms / 1000).toFixed(1)}s`;\n            return `${(ms / 60000).toFixed(1)}m`;\n        };\n\n        const formatBytes = (str) =&gt; {\n            const bytes = new Blob([str]).size;\n            if (bytes &lt; 1024) return `${bytes}B`;\n            if (bytes &lt; 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\n            return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n        };\n\n        const getDurationClass = (ms, max) =&gt; {\n            const ratio = ms / max;\n            if (ratio &gt; 0.8) return 'critical';\n            if (ratio &gt; 0.5) return 'slow';\n            if (ratio &gt; 0.2) return 'medium';\n            return 'fast';\n        };\n\n        const getDurationColor = (ms) =&gt; {\n            if (ms &lt; 100) return '#10b981';\n            if (ms &lt; 500) return '#3b82f6';\n            if (ms &lt; 2000) return '#f59e0b';\n            return '#ef4444';\n        };\n\n        // Rendering\n        const renderSidebar = () =&gt; {\n            const container = document.getElementById('sessionList');\n            container.innerHTML = '';\n\n            processedSessions.forEach(session =&gt; {\n                const item = document.createElement('div');\n                item.className = `session-item ${session.session_id === state.currentSessionId ? 'active' : ''} ${session.hasFailedSpans ? 'has-failure' : ''}`;\n                item.onclick = () =&gt; switchSession(session.session_id);\n\n                const statusClass = session.displayStatus === 'failed' ? 'status-failed' : \n                                   session.displayStatus === 'partial' ? 'status-partial' : 'status-success';\n                \n                const statusText = session.displayStatus === 'partial' ? 'Warning' : session.status;\n\n                item.innerHTML = `\n                    \n${session.name}\n                    \n\n                        ${new Date(session.start_ts).toLocaleTimeString()}\n                        \n                            ${statusText}\n                            ${session.hasFailedSpans ? '\u25cf' : ''}\n                        \n                    \n                `;\n                container.appendChild(item);\n            });\n        };\n\n        const renderSpanTree = (span, session, level = 0) =&gt; {\n            const sessionState = {\n                expandedSpans: state.expandedSpans.get(session.session_id),\n                expandedDetails: state.expandedDetails.get(session.session_id)\n            };\n\n            const isExpanded = sessionState.expandedSpans.has(span.span_id);\n            const isDetailExpanded = sessionState.expandedDetails.has(span.span_id);\n            const hasChildren = span.children &amp;&amp; span.children.length &gt; 0;\n\n            const div = document.createElement('div');\n            div.className = 'span-item';\n\n            // Row\n            const row = document.createElement('div');\n            row.className = `span-row ${span.status === 'failed' ? 'failed' : ''}`;\n            row.onclick = (e) =&gt; {\n                if (e.target.closest('.toggle-btn')) return;\n                toggleSpanDetail(session.session_id, span.span_id);\n            };\n\n            // Indentation with tree lines\n            const indent = document.createElement('div');\n            indent.className = 'span-indent';\n            indent.style.width = `${level * 20 + 20}px`;\n            indent.style.minWidth = `${level * 20 + 20}px`;\n            \n            if (hasChildren) {\n                indent.innerHTML = `\n                    \n                        \u25bc\n                    \n                `;\n            } else {\n                indent.innerHTML = '';\n            }\n\n            // Status icon\n            const statusIcon = span.status === 'failed' \n                ? '\u2715'\n                : '\u2713';\n\n            // Duration bar\n            const durationClass = getDurationClass(span.duration_ms, session.maxDuration);\n            const durationPercent = (span.duration_ms / session.maxDuration) * 100;\n\n            row.innerHTML = `\n                ${indent.outerHTML}\n                \n\n                    \n\n                        ${statusIcon}\n                        ${span.name}\n                        ${span.tool}\n                        ${span.status === 'failed' ? 'FAILED' : ''}\n                    \n                \n                \n\n                    \n\n                        \n\n                    \n                    \n${formatDuration(span.duration_ms)}\n                \n            `;\n\n            div.appendChild(row);\n\n            // Detail panel\n            const detail = document.createElement('div');\n            detail.className = `span-detail ${isDetailExpanded ? 'expanded' : ''}`;\n            \n            const inputStr = typeof span.input === 'object' ? JSON.stringify(span.input, null, 2) : String(span.input);\n            const outputStr = typeof span.output === 'object' ? JSON.stringify(span.output, null, 2) : String(span.output);\n            \n            detail.innerHTML = `\n                \n\n                    \n\n                        Input\n                        ${formatBytes(inputStr)}\n                    \n                    \n${span.input ? escapeHtml(inputStr) : 'No input'}\n                \n                \n\n                    \n\n                        Output\n                        ${formatBytes(outputStr)}\n                    \n                    \n${span.output ? escapeHtml(outputStr) : 'No output'}\n                \n            `;\n            div.appendChild(detail);\n\n            // Children\n            if (hasChildren &amp;&amp; isExpanded) {\n                const childrenContainer = document.createElement('div');\n                childrenContainer.className = 'span-children';\n                span.children.forEach(child =&gt; {\n                    childrenContainer.appendChild(renderSpanTree(child, session, level + 1));\n                });\n                div.appendChild(childrenContainer);\n            }\n\n            return div;\n        };\n\n        const escapeHtml = (text) =&gt; {\n            const div = document.createElement('div');\n            div.textContent = text;\n            return div.innerHTML;\n        };\n\n        const renderMain = () =&gt; {\n            const header = document.getElementById('mainHeader');\n            const container = document.getElementById('traceContainer');\n\n            if (!state.currentSessionId) {\n                header.innerHTML = `\n                    \n\n                        \n\u9009\u62e9 Session \u67e5\u770b\u8be6\u60c5\n                        \n\u4ece\u5de6\u4fa7\u5217\u8868\u9009\u62e9\u4e00\u4e2a\u8ffd\u8e2a\u4f1a\u8bdd\n                    \n                `;\n                container.style.display = 'none';\n                return;\n            }\n\n            const session = processedSessions.find(s =&gt; s.session_id === state.currentSessionId);\n            container.style.display = 'block';\n\n            // Header\n            const statusClass = session.displayStatus === 'failed' ? 'status-failed' : \n                               session.displayStatus === 'partial' ? 'status-partial' : 'status-success';\n            const statusText = session.displayStatus === 'partial' ? 'Completed with Failures' : session.status;\n\n            header.innerHTML = `\n                \n\n                    ${session.name}\n                    ${statusText}\n                \n                \n\n                    \n\n                        ID:\n                        ${session.session_id}\n                    \n                    \n\n                        \u5f00\u59cb\u65f6\u95f4:\n                        ${new Date(session.start_ts).toLocaleString()}\n                    \n                    \n\n                        \u603b\u8017\u65f6:\n                        ${formatDuration(session.duration_ms)}\n                    \n                    \n\n                        Storylines:\n                        ${session.storylines.length}\n                    \n                \n            `;\n\n            // Storylines\n            container.innerHTML = '';\n            const expandedStorylines = state.expandedStorylines.get(session.session_id);\n\n            session.storylines.forEach(storyline =&gt; {\n                const isExpanded = expandedStorylines.has(storyline.storyline_id);\n                \n                const storylineEl = document.createElement('div');\n                storylineEl.className = 'storyline';\n\n                // Check if storyline has failed spans\n                const hasFailure = storyline.spans.some(s =&gt; s.status === 'failed');\n\n                storylineEl.innerHTML = `\n                    \n\n                        \n\n                            \u25bc\n                            ${storyline.name}\n                            ${hasFailure ? '\u25cf' : ''}\n                        \n                        \n\n                            ${storyline.spans.length} spans\n                        \n                    \n                    \n\n                        \n\n                            ${storyline.spanRoots.map(span =&gt; renderSpanTree(span, session).outerHTML).join('')}\n                        \n                    \n                `;\n                \n                container.appendChild(storylineEl);\n            });\n        };\n\n        // Actions\n        window.toggleStoryline = (sessionId, storylineId) =&gt; {\n            const expanded = state.expandedStorylines.get(sessionId);\n            if (expanded.has(storylineId)) {\n                expanded.delete(storylineId);\n            } else {\n                expanded.add(storylineId);\n            }\n            renderMain();\n        };\n\n        window.toggleSpan = (sessionId, spanId) =&gt; {\n            const expanded = state.expandedSpans.get(sessionId);\n            if (expanded.has(spanId)) {\n                expanded.delete(spanId);\n            } else {\n                expanded.add(spanId);\n            }\n            renderMain();\n        };\n\n        window.toggleSpanDetail = (sessionId, spanId) =&gt; {\n            const expanded = state.expandedDetails.get(sessionId);\n            if (expanded.has(spanId)) {\n                expanded.delete(spanId);\n            } else {\n                expanded.add(spanId);\n            }\n            renderMain();\n        };\n\n        const switchSession = (sessionId) =&gt; {\n            state.currentSessionId = sessionId;\n            renderSidebar();\n            renderMain();\n        };\n\n        // Init\n        renderSidebar();\n        // Auto-select first session\n        if (processedSessions.length &gt; 0) {\n            switchSession(processedSessions[0].session_id);\n        }\n    \n\n", "creation_timestamp": "2026-06-14T08:34:51.000000Z"}