logbubble 0.1.0 → 0.2.0

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.
@@ -0,0 +1,384 @@
1
+ export const UI_STYLES = `
2
+ #dev-net-logger-root * {
3
+ box-sizing: border-box;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ #dev-net-logger-button {
9
+ position: fixed;
10
+ top: 35px;
11
+ right: 35px;
12
+ width: 48px;
13
+ height: 48px;
14
+ border-radius: 50%;
15
+ background: #2563eb;
16
+ color: white;
17
+ border: none;
18
+ font-size: 18px;
19
+ font-weight: bold;
20
+ cursor: move;
21
+ z-index: 999999;
22
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ font-family: monospace;
27
+ touch-action: none;
28
+ user-select: none;
29
+ }
30
+
31
+ #dev-net-logger-button:active {
32
+ transform: scale(0.95);
33
+ }
34
+
35
+ #dev-net-logger-badge {
36
+ position: absolute;
37
+ top: -4px;
38
+ right: -4px;
39
+ min-width: 18px;
40
+ height: 18px;
41
+ border-radius: 9px;
42
+ background: #ef4444;
43
+ color: white;
44
+ font-size: 10px;
45
+ font-weight: bold;
46
+ display: none;
47
+ align-items: center;
48
+ justify-content: center;
49
+ padding: 0 4px;
50
+ font-family: monospace;
51
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
52
+ pointer-events: none;
53
+ }
54
+
55
+ #dev-net-logger-badge.visible {
56
+ display: flex;
57
+ }
58
+
59
+ #dev-net-logger-backdrop {
60
+ position: fixed;
61
+ top: 0;
62
+ left: 0;
63
+ right: 0;
64
+ bottom: 0;
65
+ background: rgba(0, 0, 0, 0.4);
66
+ z-index: 999997;
67
+ display: none;
68
+ pointer-events: none;
69
+ }
70
+
71
+ #dev-net-logger-backdrop.visible {
72
+ display: block;
73
+ pointer-events: auto;
74
+ }
75
+
76
+ #dev-net-logger-detail-backdrop {
77
+ position: fixed;
78
+ top: 0;
79
+ left: 0;
80
+ right: 0;
81
+ bottom: 0;
82
+ background: rgba(0, 0, 0, 0.55);
83
+ z-index: 1000000;
84
+ display: none;
85
+ pointer-events: none;
86
+ }
87
+
88
+ #dev-net-logger-detail-backdrop.visible {
89
+ display: block;
90
+ pointer-events: auto;
91
+ }
92
+
93
+ #dev-net-logger-detail {
94
+ position: fixed;
95
+ left: 50%;
96
+ top: 50%;
97
+ transform: translate(-50%, -50%);
98
+ width: calc(100vw - 24px);
99
+ max-width: 520px;
100
+ max-height: 70vh;
101
+ background: #0b1220;
102
+ border: 1px solid #334155;
103
+ border-radius: 10px;
104
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6);
105
+ z-index: 1000001;
106
+ display: none;
107
+ flex-direction: column;
108
+ overflow: hidden;
109
+ pointer-events: none;
110
+ }
111
+
112
+ #dev-net-logger-detail.visible {
113
+ display: flex;
114
+ pointer-events: auto;
115
+ }
116
+
117
+ #dev-net-logger-detail-header {
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: space-between;
121
+ padding: 10px 12px;
122
+ background: #111827;
123
+ border-bottom: 1px solid #1f2937;
124
+ }
125
+
126
+ #dev-net-logger-detail-title {
127
+ color: #e2e8f0;
128
+ font-size: 12px;
129
+ font-family: monospace;
130
+ font-weight: 700;
131
+ overflow: hidden;
132
+ text-overflow: ellipsis;
133
+ white-space: nowrap;
134
+ }
135
+
136
+ #dev-net-logger-detail-actions {
137
+ display: flex;
138
+ gap: 8px;
139
+ }
140
+
141
+ #dev-net-logger-detail-actions button {
142
+ padding: 6px 10px;
143
+ font-size: 11px;
144
+ background: #334155;
145
+ color: #f1f5f9;
146
+ border: none;
147
+ border-radius: 6px;
148
+ cursor: pointer;
149
+ font-family: monospace;
150
+ }
151
+
152
+ #dev-net-logger-detail-actions button:hover {
153
+ background: #475569;
154
+ }
155
+
156
+ #dev-net-logger-detail-body {
157
+ padding: 12px;
158
+ margin: 0;
159
+ color: #e2e8f0;
160
+ font-size: 12px;
161
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
162
+ "Liberation Mono", "Courier New", monospace;
163
+ white-space: pre-wrap;
164
+ word-break: break-word;
165
+ overflow: auto;
166
+ user-select: text;
167
+ }
168
+
169
+ #dev-net-logger-window {
170
+ position: fixed;
171
+ top: 70px;
172
+ right: 10px;
173
+ width: calc(100vw - 20px);
174
+ max-width: 500px;
175
+ height: 60vh;
176
+ background: rgba(0, 0, 0, 0.95);
177
+ border-radius: 8px;
178
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
179
+ z-index: 999998;
180
+ display: none;
181
+ flex-direction: column;
182
+ overflow: hidden;
183
+ transition: none;
184
+ pointer-events: none;
185
+ }
186
+
187
+ #dev-net-logger-window.visible {
188
+ display: flex;
189
+ pointer-events: auto;
190
+ }
191
+
192
+ #dev-net-logger-window.position-top {
193
+ top: 70px;
194
+ bottom: auto;
195
+ }
196
+
197
+ #dev-net-logger-window.position-bottom {
198
+ bottom: 70px;
199
+ top: auto;
200
+ }
201
+
202
+ #dev-net-logger-window.position-left {
203
+ left: 10px;
204
+ right: auto;
205
+ }
206
+
207
+ #dev-net-logger-window.position-right {
208
+ right: 10px;
209
+ left: auto;
210
+ }
211
+
212
+ #dev-net-logger-header {
213
+ display: flex;
214
+ justify-content: space-between;
215
+ align-items: center;
216
+ padding: 12px 16px;
217
+ background: #1e293b;
218
+ border-bottom: 1px solid #334155;
219
+ }
220
+
221
+ #dev-net-logger-title {
222
+ color: #f1f5f9;
223
+ font-size: 14px;
224
+ font-weight: bold;
225
+ font-family: monospace;
226
+ }
227
+
228
+ #dev-net-logger-controls {
229
+ display: flex;
230
+ gap: 8px;
231
+ }
232
+
233
+ #dev-net-logger-controls button {
234
+ padding: 6px 12px;
235
+ font-size: 11px;
236
+ background: #334155;
237
+ color: #f1f5f9;
238
+ border: none;
239
+ border-radius: 4px;
240
+ cursor: pointer;
241
+ font-family: monospace;
242
+ font-weight: 500;
243
+ transition: background 0.15s;
244
+ min-width: 28px;
245
+ min-height: 28px;
246
+ }
247
+
248
+ #dev-net-logger-controls button:hover {
249
+ background: #475569;
250
+ }
251
+
252
+ #dev-net-logger-controls button:active {
253
+ transform: scale(0.95);
254
+ }
255
+
256
+ #dev-net-logger-controls button.filter-active {
257
+ background: #3b82f6;
258
+ color: white;
259
+ }
260
+
261
+ #dev-net-logger-content {
262
+ flex: 1;
263
+ overflow-y: auto;
264
+ overflow-x: hidden;
265
+ background: #0f172a;
266
+ position: relative;
267
+ padding-top: 4px;
268
+ }
269
+
270
+ #dev-net-logger-scroll-container {
271
+ position: relative;
272
+ width: 100%;
273
+ }
274
+
275
+ #dev-net-logger-viewport {
276
+ position: absolute;
277
+ top: 0;
278
+ left: 0;
279
+ right: 0;
280
+ padding: 0;
281
+ }
282
+
283
+ .log-entry {
284
+ height: 40px;
285
+ padding: 8px 10px;
286
+ border-radius: 4px;
287
+ background: rgba(15, 23, 42, 0.95);
288
+ border: 1px solid rgba(148, 163, 184, 0.14);
289
+ border-left: 3px solid #64748b;
290
+ display: flex;
291
+ align-items: center;
292
+ overflow: hidden;
293
+ contain: layout style paint;
294
+ will-change: transform;
295
+ cursor: pointer;
296
+ }
297
+
298
+ .log-entry.type-fetch {
299
+ border-left-color: #3b82f6;
300
+ }
301
+
302
+ .log-entry.type-xhr {
303
+ border-left-color: #8b5cf6;
304
+ }
305
+
306
+ .log-entry.type-dom {
307
+ border-left-color: #f59e0b;
308
+ }
309
+
310
+ .log-entry.type-plugin {
311
+ border-left-color: #ec4899;
312
+ }
313
+
314
+ .log-entry.type-console {
315
+ border-left-color: #10b981;
316
+ }
317
+
318
+ .log-entry.critical {
319
+ background: rgba(220, 38, 38, 0.14);
320
+ border-left-color: #ef4444;
321
+ }
322
+
323
+ .log-entry.critical .log-timestamp {
324
+ color: #fca5a5;
325
+ }
326
+
327
+ .log-entry.critical .log-message {
328
+ color: #fee2e2;
329
+ font-weight: 600;
330
+ }
331
+
332
+ .large-log-indicator {
333
+ color: #fef2f2;
334
+ text-decoration: underline;
335
+ cursor: pointer;
336
+ font-weight: bold;
337
+ }
338
+
339
+ .log-entry.critical .large-log-indicator {
340
+ color: #fff1f2;
341
+ }
342
+
343
+ .log-entry.critical .large-log-indicator:hover {
344
+ color: #fee2e2;
345
+ text-decoration: none;
346
+ }
347
+
348
+ .log-timestamp {
349
+ color: #64748b;
350
+ font-size: 10px;
351
+ margin-left: 2px !important;
352
+ margin-right: 2px !important;
353
+ flex-shrink: 0;
354
+ }
355
+
356
+ .log-message {
357
+ color: #e2e8f0;
358
+ flex: 1;
359
+ min-width: 0;
360
+ white-space: nowrap;
361
+ overflow: hidden;
362
+ text-overflow: ellipsis;
363
+ }
364
+
365
+ .log-count-badge {
366
+ display: inline-flex;
367
+ align-items: center;
368
+ justify-content: center;
369
+ margin-left: 8px;
370
+ padding: 2px 6px;
371
+ background: #3b82f6;
372
+ color: white;
373
+ font-size: 10px;
374
+ font-weight: bold;
375
+ border-radius: 10px;
376
+ min-width: 18px;
377
+ flex-shrink: 0;
378
+ }
379
+
380
+ .log-entry.critical .log-count-badge {
381
+ background: #991b1b;
382
+ color: #fef2f2;
383
+ }
384
+ `;
@@ -0,0 +1,24 @@
1
+ import { LogEntry } from "./logStore";
2
+ export declare class VirtualScroller {
3
+ private scrollContainerEl;
4
+ private viewportEl;
5
+ private contentEl;
6
+ private createLogElement;
7
+ private itemHeight;
8
+ private overscan;
9
+ private scrollTop;
10
+ private viewportHeight;
11
+ private lastRenderStart;
12
+ private lastRenderEnd;
13
+ private currentLogs;
14
+ private perfMonitor;
15
+ constructor(scrollContainerEl: HTMLDivElement, viewportEl: HTMLDivElement, contentEl: HTMLDivElement, createLogElement: (log: LogEntry) => HTMLDivElement);
16
+ attachScrollListener(): void;
17
+ renderVisibleLogs(filteredLogs: LogEntry[]): void;
18
+ private renderCurrentView;
19
+ scrollToBottom(): void;
20
+ getViewportHeight(): number;
21
+ updateViewportHeight(): void;
22
+ private handleScroll;
23
+ private updateScrollContainerHeight;
24
+ }
@@ -0,0 +1,112 @@
1
+ export class VirtualScroller {
2
+ constructor(scrollContainerEl, viewportEl, contentEl, createLogElement) {
3
+ this.scrollContainerEl = scrollContainerEl;
4
+ this.viewportEl = viewportEl;
5
+ this.contentEl = contentEl;
6
+ this.createLogElement = createLogElement;
7
+ this.itemHeight = 48;
8
+ this.overscan = 5;
9
+ this.scrollTop = 0;
10
+ this.viewportHeight = 0;
11
+ this.lastRenderStart = -1;
12
+ this.lastRenderEnd = -1;
13
+ this.currentLogs = [];
14
+ this.perfMonitor = {
15
+ scrollFrameTimes: [],
16
+ };
17
+ }
18
+ attachScrollListener() {
19
+ this.contentEl.addEventListener("scroll", () => this.handleScroll());
20
+ const resizeObserver = new ResizeObserver(() => {
21
+ this.viewportHeight = this.contentEl.clientHeight;
22
+ this.renderCurrentView();
23
+ });
24
+ resizeObserver.observe(this.contentEl);
25
+ }
26
+ renderVisibleLogs(filteredLogs) {
27
+ this.currentLogs = filteredLogs;
28
+ this.lastRenderStart = -1;
29
+ this.lastRenderEnd = -1;
30
+ this.renderCurrentView();
31
+ }
32
+ renderCurrentView() {
33
+ this.scrollTop = this.contentEl.scrollTop;
34
+ if (this.viewportHeight === 0) {
35
+ this.viewportHeight = this.contentEl.clientHeight;
36
+ }
37
+ const filteredLogs = this.currentLogs;
38
+ const totalLogs = filteredLogs.length;
39
+ if (totalLogs === 0) {
40
+ this.viewportEl.innerHTML = "";
41
+ this.scrollContainerEl.style.height = "0px";
42
+ return;
43
+ }
44
+ const totalHeight = totalLogs * this.itemHeight;
45
+ const maxScrollTop = Math.max(0, totalHeight - this.viewportHeight);
46
+ if (this.scrollTop > maxScrollTop) {
47
+ this.scrollTop = maxScrollTop;
48
+ this.contentEl.scrollTop = maxScrollTop;
49
+ }
50
+ const startIndex = Math.floor(this.scrollTop / this.itemHeight);
51
+ const endIndex = Math.ceil((this.scrollTop + this.viewportHeight) / this.itemHeight);
52
+ const renderStart = Math.max(0, startIndex - this.overscan);
53
+ const renderEnd = Math.min(totalLogs, endIndex + this.overscan);
54
+ if (renderStart >= renderEnd) {
55
+ this.scrollTop = 0;
56
+ this.contentEl.scrollTop = 0;
57
+ this.lastRenderStart = -1;
58
+ this.lastRenderEnd = -1;
59
+ this.renderCurrentView();
60
+ return;
61
+ }
62
+ if (renderStart === this.lastRenderStart &&
63
+ renderEnd === this.lastRenderEnd) {
64
+ return;
65
+ }
66
+ this.lastRenderStart = renderStart;
67
+ this.lastRenderEnd = renderEnd;
68
+ this.updateScrollContainerHeight(totalLogs);
69
+ const fragment = document.createDocumentFragment();
70
+ for (let i = renderStart; i < renderEnd; i++) {
71
+ const logEl = this.createLogElement(filteredLogs[i]);
72
+ logEl.style.position = "absolute";
73
+ logEl.style.top = `${i * this.itemHeight}px`;
74
+ logEl.style.left = "8px";
75
+ logEl.style.right = "8px";
76
+ fragment.appendChild(logEl);
77
+ }
78
+ this.viewportEl.innerHTML = "";
79
+ this.viewportEl.appendChild(fragment);
80
+ }
81
+ scrollToBottom() {
82
+ if (this.contentEl) {
83
+ this.contentEl.scrollTop = this.contentEl.scrollHeight;
84
+ }
85
+ }
86
+ getViewportHeight() {
87
+ return this.viewportHeight;
88
+ }
89
+ updateViewportHeight() {
90
+ this.viewportHeight = this.contentEl.clientHeight;
91
+ }
92
+ handleScroll() {
93
+ const frameStart = performance.now();
94
+ this.scrollTop = this.contentEl.scrollTop;
95
+ this.renderCurrentView();
96
+ const frameTime = performance.now() - frameStart;
97
+ this.perfMonitor.scrollFrameTimes.push(frameTime);
98
+ if (this.perfMonitor.scrollFrameTimes.length > 60) {
99
+ this.perfMonitor.scrollFrameTimes.shift();
100
+ }
101
+ const avgFrameTime = this.perfMonitor.scrollFrameTimes.reduce((a, b) => a + b, 0) /
102
+ this.perfMonitor.scrollFrameTimes.length;
103
+ const fps = 1000 / avgFrameTime;
104
+ if (fps < 55 && this.perfMonitor.scrollFrameTimes.length >= 60) {
105
+ console.warn(`[LogBubble] Scroll performance: ${fps.toFixed(1)} FPS (target: ≥55 FPS)`);
106
+ }
107
+ }
108
+ updateScrollContainerHeight(totalLogs) {
109
+ const totalHeight = totalLogs * this.itemHeight;
110
+ this.scrollContainerEl.style.height = `${totalHeight}px`;
111
+ }
112
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "vite";
2
+ declare const logBubblePlugin: () => Plugin;
3
+ export default logBubblePlugin;
@@ -0,0 +1,20 @@
1
+ const logBubblePlugin = () => {
2
+ return {
3
+ name: "vite-logbubble",
4
+ enforce: "pre",
5
+ transform(code, id) {
6
+ // Only inject into main entry files
7
+ if (id.endsWith("main.ts") ||
8
+ id.endsWith("main.js") ||
9
+ id.endsWith("main.jsx") ||
10
+ id.endsWith("main.tsx")) {
11
+ const inject = `import 'logbubble';\n`;
12
+ if (!code.includes("initNetworkLogger")) {
13
+ return inject + code;
14
+ }
15
+ }
16
+ return code;
17
+ },
18
+ };
19
+ };
20
+ export default logBubblePlugin;
package/package.json CHANGED
@@ -1,18 +1,32 @@
1
1
  {
2
2
  "name": "logbubble",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "LogBubble: Web floating logger for network and console logs",
5
5
  "main": "dist/init.js",
6
6
  "types": "dist/init.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/init.d.ts",
10
+ "import": "./dist/init.js",
11
+ "require": "./dist/init.js"
12
+ },
13
+ "./vite-logbubble-plugin": {
14
+ "types": "./dist/vite-logbubble-plugin.d.ts",
15
+ "import": "./dist/vite-logbubble-plugin.js",
16
+ "require": "./dist/vite-logbubble-plugin.js"
17
+ },
18
+ "./auto": {
19
+ "types": "./dist/auto.d.ts",
20
+ "import": "./dist/auto.js",
21
+ "require": "./dist/auto.js"
22
+ }
23
+ },
7
24
  "files": [
8
- "dist",
9
- "core",
10
- "ui",
11
- "init.ts",
12
- "auto.ts"
25
+ "dist"
13
26
  ],
14
27
  "sideEffects": [
15
- "./auto.ts"
28
+ "dist/auto.js",
29
+ "dist/init.js"
16
30
  ],
17
31
  "scripts": {
18
32
  "build": "tsc",
@@ -20,7 +34,8 @@
20
34
  },
21
35
  "devDependencies": {
22
36
  "@types/node": "^25.0.9",
23
- "typescript": "^5.0.0"
37
+ "typescript": "^5.0.0",
38
+ "vite": "^7.3.1"
24
39
  },
25
40
  "keywords": [
26
41
  "logbubble",
@@ -32,6 +47,6 @@
32
47
  "ui",
33
48
  "bubble"
34
49
  ],
35
- "author": "",
50
+ "author": "Deepak Kumar",
36
51
  "license": "MIT"
37
52
  }
package/auto.ts DELETED
@@ -1,4 +0,0 @@
1
- // auto.ts
2
- // Side-effect import for auto-initialization
3
- import { initNetworkLogger } from "./init";
4
- initNetworkLogger();
package/core/console.ts DELETED
@@ -1,79 +0,0 @@
1
- // console.ts
2
- // Patches console methods to log to the network logger UI
3
-
4
- import { logStore } from "../ui/logStore";
5
-
6
- let isPatching = false;
7
-
8
- export function patchConsole() {
9
- if (typeof window === "undefined" || typeof console === "undefined") return;
10
-
11
- const originalLog = console.log;
12
- const originalWarn = console.warn;
13
- const originalError = console.error;
14
- const originalInfo = console.info;
15
- const originalDebug = console.debug;
16
-
17
- console.log = function (...args: any[]) {
18
- originalLog.apply(console, args);
19
- if (!isPatching) {
20
- isPatching = true;
21
- const message = args.map((arg) => formatArg(arg)).join(" ");
22
- logStore.addLog(`[LOG] ${message}`, "plugin");
23
- isPatching = false;
24
- }
25
- };
26
-
27
- console.warn = function (...args: any[]) {
28
- originalWarn.apply(console, args);
29
- if (!isPatching) {
30
- isPatching = true;
31
- const message = args.map((arg) => formatArg(arg)).join(" ");
32
- logStore.addLog(`[WARN] ${message}`, "plugin");
33
- isPatching = false;
34
- }
35
- };
36
-
37
- console.error = function (...args: any[]) {
38
- originalError.apply(console, args);
39
- if (!isPatching) {
40
- isPatching = true;
41
- const message = args.map((arg) => formatArg(arg)).join(" ");
42
- logStore.addLog(`[ERROR] ${message}`, "plugin");
43
- isPatching = false;
44
- }
45
- };
46
-
47
- console.info = function (...args: any[]) {
48
- originalInfo.apply(console, args);
49
- if (!isPatching) {
50
- isPatching = true;
51
- const message = args.map((arg) => formatArg(arg)).join(" ");
52
- logStore.addLog(`[INFO] ${message}`, "plugin");
53
- isPatching = false;
54
- }
55
- };
56
-
57
- console.debug = function (...args: any[]) {
58
- originalDebug.apply(console, args);
59
- if (!isPatching) {
60
- isPatching = true;
61
- const message = args.map((arg) => formatArg(arg)).join(" ");
62
- logStore.addLog(`[DEBUG] ${message}`, "plugin");
63
- isPatching = false;
64
- }
65
- };
66
- }
67
-
68
- function formatArg(arg: any): string {
69
- if (arg === null) return "null";
70
- if (arg === undefined) return "undefined";
71
- if (typeof arg === "string") return arg;
72
- if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
73
- if (arg instanceof Error) return `${arg.name}: ${arg.message}`;
74
- try {
75
- return JSON.stringify(arg);
76
- } catch {
77
- return String(arg);
78
- }
79
- }