@neuradigi/debug-console 0.1.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.
- package/LICENSE +21 -0
- package/README.md +237 -0
- package/dist/index.cjs +266 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +158 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +266 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Neuradigi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# @neuradigi/debug-console
|
|
2
|
+
|
|
3
|
+
A tiny **in-app console overlay**. It mirrors your app's `console.log/info/warn/error/debug`
|
|
4
|
+
and uncaught errors into a floating panel, so you can read logs **inside the running app** β
|
|
5
|
+
no DevTools needed. It *mirrors* output (never swallows it), so DevTools keeps working too.
|
|
6
|
+
|
|
7
|
+
Built as a **Web Component** (Shadow DOM), so it drops into **Angular, React, Vue, Svelte,
|
|
8
|
+
or plain HTML** with **zero dependencies** and no style conflicts.
|
|
9
|
+
|
|
10
|
+
- π One call to set up
|
|
11
|
+
- π― Captures `console.*` + uncaught errors / promise rejections
|
|
12
|
+
- π§° Filter, auto-scroll, download `.log`, clear
|
|
13
|
+
- π§± Style-isolated, SSR-safe, zero runtime deps
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @neuradigi/debug-console
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
No bundler? Import straight from a CDN β nothing to install:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import { initDebugConsole } from 'https://esm.sh/@neuradigi/debug-console';
|
|
26
|
+
initDebugConsole();
|
|
27
|
+
</script>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
Call it **once, as early as possible** in your app's entry file:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { initDebugConsole } from '@neuradigi/debug-console';
|
|
36
|
+
|
|
37
|
+
initDebugConsole();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A launcher button appears in the corner β click it to open the panel. That's it.
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
Everything is optional β pass only what you want to change:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
initDebugConsole({
|
|
48
|
+
enabled: true, // false = do nothing at all (use to switch off in production)
|
|
49
|
+
max: 500, // max lines kept; oldest are dropped past this
|
|
50
|
+
captureGlobalErrors: true, // also catch uncaught errors + unhandled promise rejections
|
|
51
|
+
position: 'top-right', // 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
|
|
52
|
+
accent: '#22c55e', // any CSS color/gradient for the launcher & active filter chips
|
|
53
|
+
open: false // true = start expanded instead of collapsed
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Turn it off in production:** `initDebugConsole({ enabled: process.env.NODE_ENV !== 'production' })`.
|
|
58
|
+
|
|
59
|
+
## Framework setup
|
|
60
|
+
|
|
61
|
+
It's the same everywhere: **call `initDebugConsole()` at the top of your entry file,
|
|
62
|
+
before your app mounts** β so even startup logs are caught. No tags or registration needed.
|
|
63
|
+
|
|
64
|
+
| Framework | Entry file | Put the call⦠|
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| Angular | `src/main.ts` | before `bootstrapApplication(...)` |
|
|
67
|
+
| React | `src/main.tsx` | before `createRoot(...).render(...)` |
|
|
68
|
+
| Vue | `src/main.ts` | before `createApp(...).mount(...)` |
|
|
69
|
+
| Svelte | `src/main.ts` | before `new App(...)` |
|
|
70
|
+
| SvelteKit | `src/routes/+layout.svelte` | inside `onMount(() => ...)` |
|
|
71
|
+
| Plain HTML | your page `<head>` | the CDN snippet from [Install](#install) |
|
|
72
|
+
|
|
73
|
+
<details>
|
|
74
|
+
<summary><b>Rather place the <code><debug-console></code> tag in your own markup?</b></summary>
|
|
75
|
+
|
|
76
|
+
Start capture without auto-mounting, register the element, then use the tag:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { defineDebugConsole, startCapture } from '@neuradigi/debug-console';
|
|
80
|
+
|
|
81
|
+
startCapture(); // begin mirroring console.* + errors (no UI)
|
|
82
|
+
defineDebugConsole(); // register the <debug-console> element
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<debug-console data-position="top-right"></debug-console>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Framework-specific bits when you use the tag in a template:
|
|
90
|
+
|
|
91
|
+
- **Angular** β add `CUSTOM_ELEMENTS_SCHEMA` to the component:
|
|
92
|
+
```ts
|
|
93
|
+
@Component({ /* ... */, schemas: [CUSTOM_ELEMENTS_SCHEMA] })
|
|
94
|
+
```
|
|
95
|
+
- **React (TypeScript)** β declare the tag once (any `.d.ts`):
|
|
96
|
+
```tsx
|
|
97
|
+
declare global {
|
|
98
|
+
namespace JSX {
|
|
99
|
+
interface IntrinsicElements {
|
|
100
|
+
'debug-console': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
- **Vue** β mark it as a custom element in the compiler options:
|
|
106
|
+
```ts
|
|
107
|
+
vue({ template: { compilerOptions: { isCustomElement: t => t === 'debug-console' } } })
|
|
108
|
+
```
|
|
109
|
+
- **Svelte** β nothing to configure; unknown tags pass straight through.
|
|
110
|
+
|
|
111
|
+
(Not needed when you use `initDebugConsole()` β it registers and mounts for you.)
|
|
112
|
+
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
## Panel controls
|
|
116
|
+
|
|
117
|
+
| Control | What it does |
|
|
118
|
+
|---|---|
|
|
119
|
+
| **Launcher** (corner button) | Opens the panel. While closed, shows a badge with the error/warning count (red if any errors, amber if only warnings). |
|
|
120
|
+
| **Filter chips** | `All / Log / Info / Warn / Error` (Log also includes `debug`). |
|
|
121
|
+
| **Auto-scroll** | Toggle following the newest line. |
|
|
122
|
+
| **Scroll to bottom** | Jump to the latest entry. |
|
|
123
|
+
| **Download** | Save all logs to a timestamped `.log` file. |
|
|
124
|
+
| **Clear** | Empty the buffer. |
|
|
125
|
+
| **Close** | Collapse back to the launcher. |
|
|
126
|
+
|
|
127
|
+
Levels are colour-coded: error red, warn amber, info blue, log/debug gray.
|
|
128
|
+
|
|
129
|
+
## API
|
|
130
|
+
|
|
131
|
+
Most apps only need `initDebugConsole()`. It returns a handle for programmatic control
|
|
132
|
+
(or `null` when disabled / no DOM):
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const dc = initDebugConsole();
|
|
136
|
+
dc?.show(); // open the panel
|
|
137
|
+
dc?.hide(); // close it
|
|
138
|
+
dc?.toggle();
|
|
139
|
+
dc?.clear(); // empty the buffer
|
|
140
|
+
dc?.destroy(); // remove the overlay
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
<details>
|
|
144
|
+
<summary><b>Other exports (declarative setup, reading the buffer)</b></summary>
|
|
145
|
+
|
|
146
|
+
| Export | Purpose |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `startCapture(opts?)` | Begin mirroring `console.*` + errors, no UI. Accepts `{ max, captureGlobalErrors }`. |
|
|
149
|
+
| `defineDebugConsole(name?)` | Register the `<debug-console>` element (default tag `debug-console`). |
|
|
150
|
+
| `ELEMENT_NAME` | The default tag name (`'debug-console'`). |
|
|
151
|
+
| `logger` | The capture core β read/observe the buffer yourself. |
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { logger } from '@neuradigi/debug-console';
|
|
155
|
+
|
|
156
|
+
logger.entries; // readonly LogEntry[] (oldest β newest)
|
|
157
|
+
logger.limit; // configured buffer cap
|
|
158
|
+
logger.clear(); // empty the buffer
|
|
159
|
+
const off = logger.subscribe(e => { // e is { type: 'add', entry } | { type: 'clear' }
|
|
160
|
+
if (e.type === 'add') myTelemetry(e.entry);
|
|
161
|
+
});
|
|
162
|
+
// ...later: off();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Types exported: `DebugConsoleOptions`, `DebugConsoleHandle`, `LauncherPosition`,
|
|
166
|
+
`LogEntry`, `LogLevel`, `LogEvent`.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
170
|
+
interface LogEntry { id: number; level: LogLevel; time: Date; text: string; }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
</details>
|
|
174
|
+
|
|
175
|
+
## Theming
|
|
176
|
+
|
|
177
|
+
The panel uses a fixed dark "terminal" look. Change the accent in one line:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
initDebugConsole({ accent: '#22c55e' });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Or override any colour with a CSS custom property (they inherit through the shadow boundary):
|
|
184
|
+
|
|
185
|
+
```css
|
|
186
|
+
debug-console { --dc-accent: #22c55e; --dc-bg: #0d1117; }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
<details>
|
|
190
|
+
<summary><b>All CSS variables & shadow parts</b></summary>
|
|
191
|
+
|
|
192
|
+
| Variable | Default | Purpose |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `--dc-accent` | brand gradient | Launcher, count pill, active chips. |
|
|
195
|
+
| `--dc-bg` | `#0b0e14` | Panel background. |
|
|
196
|
+
| `--dc-surface` | `#11151f` | Header / filter bar background. |
|
|
197
|
+
| `--dc-border` | `#232a3a` | Borders and scrollbar thumb. |
|
|
198
|
+
| `--dc-text` | `#c9d1d9` | Default text. |
|
|
199
|
+
| `--dc-muted` | `#8b949e` | Timestamps, muted / log level. |
|
|
200
|
+
| `--dc-row-hover` | `rgba(255,255,255,.04)` | Row hover background. |
|
|
201
|
+
| `--dc-shadow` | `0 10px 34px rgba(0,0,0,.5)` | Panel & launcher shadow. |
|
|
202
|
+
| `--dc-error` | `#ff6b6b` | Error level. |
|
|
203
|
+
| `--dc-warn` | `#ffc107` | Warn level. |
|
|
204
|
+
| `--dc-info` | `#57bdff` | Info level + active toolbar icons. |
|
|
205
|
+
| `--dc-badge-error` | `#dc3545` | Launcher error badge. |
|
|
206
|
+
| `--dc-badge-warn` | `#ffc107` | Launcher warning badge. |
|
|
207
|
+
|
|
208
|
+
Two shadow parts are exposed for structural styling:
|
|
209
|
+
|
|
210
|
+
```css
|
|
211
|
+
debug-console::part(launcher) { /* the corner button */ }
|
|
212
|
+
debug-console::part(panel) { /* the sliding panel */ }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
</details>
|
|
216
|
+
|
|
217
|
+
## Notes & limits
|
|
218
|
+
|
|
219
|
+
- Captures `log / info / warn / error / debug`. Not `console.trace/table/dir/group/assert`.
|
|
220
|
+
- Native browser messages (failed network / CORS / CSP) only appear if your code logs them.
|
|
221
|
+
- Buffer is bounded (default 500); safely stringifies circular refs, `bigint`, and `Error`.
|
|
222
|
+
- Uses `position: fixed` β a `transform`/`filter` on an ancestor re-anchors it (standard CSS).
|
|
223
|
+
- SSR-safe: every call no-ops when there is no DOM.
|
|
224
|
+
|
|
225
|
+
## Develop
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
npm install
|
|
229
|
+
npm run build # bundle to dist/ (ESM + CJS + types)
|
|
230
|
+
npm run dev # rebuild on change
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Live demo: run `npm run build`, serve the repo root (`npx serve .`), and open `/demo/`.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
MIT Β© Neuradigi Technologies
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'use strict';var d=e=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${e}</svg>`,c={terminal:d('<rect x="3" y="4" width="18" height="16" rx="2"/><path d="m7 9 3 3-3 3"/><path d="M13 15h4"/>'),pin:d('<path d="M12 17v5"/><path d="M9 10.8V6a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8"/><path d="M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.2V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.8a2 2 0 0 0-1.1-1.7l-1.8-.9A2 2 0 0 1 15 10.8V6a1 1 0 0 0-1-1"/>'),chevronsDown:d('<path d="m7 6 5 5 5-5"/><path d="m7 13 5 5 5-5"/>'),download:d('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'),trash:d('<path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m19 6-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>'),close:d('<path d="M18 6 6 18"/><path d="m6 6 12 12"/>')};var v=["log","info","warn","error","debug"],u=class{constructor(){this._entries=[];this.listeners=new Set;this.nextId=0;this.max=500;this.patched=false;this.errorsHooked=false;}get entries(){return this._entries}get limit(){return this.max}init(o={}){typeof o.max=="number"&&o.max>0&&(this.max=Math.floor(o.max)),this.patchConsole(),o.captureGlobalErrors!==false&&this.captureGlobalErrors();}subscribe(o){return this.listeners.add(o),()=>{this.listeners.delete(o);}}clear(){this._entries.length=0,this.emit({type:"clear"});}patchConsole(){if(this.patched||typeof console>"u")return;this.patched=true;let o=console;for(let t of v){let a=o[t].bind(console);o[t]=(...r)=>{a(...r),this.push(t,this.stringifyArgs(r));};}}captureGlobalErrors(){this.errorsHooked||typeof window>"u"||(this.errorsHooked=true,window.addEventListener("error",o=>{let t=o.error!=null?this.stringifyValue(o.error):o.message||"Uncaught error";this.push("error",t);}),window.addEventListener("unhandledrejection",o=>{this.push("error",`Unhandled promise rejection: ${this.stringifyValue(o.reason)}`);}));}push(o,t){let a={id:this.nextId++,level:o,time:new Date,text:t};this._entries.push(a),this._entries.length>this.max&&this._entries.splice(0,this._entries.length-this.max),this.emit({type:"add",entry:a});}emit(o){this.listeners.forEach(t=>{try{t(o);}catch{}});}stringifyArgs(o){return o.map(t=>this.stringifyValue(t)).join(" ")}stringifyValue(o){if(typeof o=="string")return o;if(o instanceof Error)return `${o.name}: ${o.message}`;try{let t=new WeakSet;return JSON.stringify(o,(r,i)=>{if(typeof i=="bigint")return i.toString();if(typeof i=="object"&&i!==null){if(t.has(i))return "[Circular]";t.add(i);}return i},2)??String(o)}catch{return String(o)}}},l=new u;var f=`
|
|
2
|
+
:host {
|
|
3
|
+
--dc-bg: #0b0e14;
|
|
4
|
+
--dc-surface: #11151f;
|
|
5
|
+
--dc-border: #232a3a;
|
|
6
|
+
--dc-text: #c9d1d9;
|
|
7
|
+
--dc-muted: #8b949e;
|
|
8
|
+
--dc-row-hover: rgba(255, 255, 255, 0.04);
|
|
9
|
+
--dc-shadow: 0 10px 34px rgba(0, 0, 0, 0.5);
|
|
10
|
+
--dc-accent: linear-gradient(135deg, #57bdff, #4f77fb, #3a5fcc);
|
|
11
|
+
--dc-error: #ff6b6b;
|
|
12
|
+
--dc-warn: #ffc107;
|
|
13
|
+
--dc-info: #57bdff;
|
|
14
|
+
--dc-badge-error: #dc3545;
|
|
15
|
+
--dc-badge-warn: #ffc107;
|
|
16
|
+
font-family: 'Cascadia Code', 'Fira Code', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
20
|
+
[hidden] { display: none !important; }
|
|
21
|
+
|
|
22
|
+
/* ===== Launcher ===== */
|
|
23
|
+
.dc-launcher {
|
|
24
|
+
position: fixed;
|
|
25
|
+
top: 16px;
|
|
26
|
+
right: 16px;
|
|
27
|
+
z-index: 2147483000;
|
|
28
|
+
width: 44px;
|
|
29
|
+
height: 44px;
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
border: none;
|
|
34
|
+
border-radius: 50%;
|
|
35
|
+
background: var(--dc-accent);
|
|
36
|
+
color: #fff;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
box-shadow: var(--dc-shadow);
|
|
39
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
40
|
+
}
|
|
41
|
+
.dc-launcher svg { width: 22px; height: 22px; }
|
|
42
|
+
.dc-launcher:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(79, 119, 251, 0.45); }
|
|
43
|
+
:host([data-position="top-left"]) .dc-launcher { top: 16px; left: 16px; right: auto; }
|
|
44
|
+
:host([data-position="bottom-right"]) .dc-launcher { top: auto; bottom: 16px; right: 16px; }
|
|
45
|
+
:host([data-position="bottom-left"]) .dc-launcher { top: auto; bottom: 16px; left: 16px; right: auto; }
|
|
46
|
+
|
|
47
|
+
/* ===== Badge ===== */
|
|
48
|
+
.dc-badge {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: -4px;
|
|
51
|
+
right: -4px;
|
|
52
|
+
min-width: 18px;
|
|
53
|
+
height: 18px;
|
|
54
|
+
padding: 0 5px;
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
font-size: 11px;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
line-height: 1;
|
|
61
|
+
border-radius: 999px;
|
|
62
|
+
border: 2px solid var(--dc-bg);
|
|
63
|
+
}
|
|
64
|
+
.dc-badge--error { background: var(--dc-badge-error); color: #fff; }
|
|
65
|
+
.dc-badge--warn { background: var(--dc-badge-warn); color: #000; }
|
|
66
|
+
|
|
67
|
+
/* ===== Panel ===== */
|
|
68
|
+
.dc-panel {
|
|
69
|
+
position: fixed;
|
|
70
|
+
top: 0;
|
|
71
|
+
left: 0;
|
|
72
|
+
right: 0;
|
|
73
|
+
z-index: 2147482000;
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
max-height: 50vh;
|
|
77
|
+
background: var(--dc-bg);
|
|
78
|
+
color: var(--dc-text);
|
|
79
|
+
border-bottom: 1px solid var(--dc-border);
|
|
80
|
+
box-shadow: var(--dc-shadow);
|
|
81
|
+
transform: translateY(-100%);
|
|
82
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
83
|
+
}
|
|
84
|
+
.dc-panel--open { transform: translateY(0); }
|
|
85
|
+
|
|
86
|
+
/* ===== Header ===== */
|
|
87
|
+
.dc-header {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
padding: 8px 16px;
|
|
93
|
+
background: var(--dc-surface);
|
|
94
|
+
border-bottom: 1px solid var(--dc-border);
|
|
95
|
+
flex-shrink: 0;
|
|
96
|
+
}
|
|
97
|
+
.dc-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
98
|
+
.dc-title-icon { display: inline-flex; color: var(--dc-info); }
|
|
99
|
+
.dc-title-icon svg { width: 20px; height: 20px; }
|
|
100
|
+
.dc-title-text { font-size: 14px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap; color: var(--dc-text); }
|
|
101
|
+
.dc-count {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
min-width: 20px;
|
|
106
|
+
height: 18px;
|
|
107
|
+
padding: 0 6px;
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
line-height: 1;
|
|
111
|
+
color: #fff;
|
|
112
|
+
background: var(--dc-accent);
|
|
113
|
+
border-radius: 999px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ===== Actions ===== */
|
|
117
|
+
.dc-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
|
|
118
|
+
.dc-action {
|
|
119
|
+
position: relative;
|
|
120
|
+
display: inline-flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
justify-content: center;
|
|
123
|
+
width: 32px;
|
|
124
|
+
height: 32px;
|
|
125
|
+
color: var(--dc-muted);
|
|
126
|
+
background: transparent;
|
|
127
|
+
border: none;
|
|
128
|
+
border-radius: 6px;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
transition: color 0.2s ease, background-color 0.2s ease;
|
|
131
|
+
}
|
|
132
|
+
.dc-action svg { width: 18px; height: 18px; }
|
|
133
|
+
.dc-action:hover:not(:disabled) { color: var(--dc-text); background: var(--dc-row-hover); }
|
|
134
|
+
.dc-action:disabled { opacity: 0.35; cursor: default; }
|
|
135
|
+
.dc-action--active { color: var(--dc-info); }
|
|
136
|
+
|
|
137
|
+
/* ===== Tooltips (self-contained; no host overlay layer) ===== */
|
|
138
|
+
.dc-action[data-tip]:hover::after,
|
|
139
|
+
.dc-launcher[data-tip]:hover::after {
|
|
140
|
+
content: attr(data-tip);
|
|
141
|
+
position: absolute;
|
|
142
|
+
padding: 4px 8px;
|
|
143
|
+
border-radius: 6px;
|
|
144
|
+
background: #000;
|
|
145
|
+
color: #fff;
|
|
146
|
+
font-size: 11px;
|
|
147
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
148
|
+
font-weight: 500;
|
|
149
|
+
white-space: nowrap;
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
z-index: 10;
|
|
152
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
|
|
153
|
+
}
|
|
154
|
+
.dc-action[data-tip]:hover::after {
|
|
155
|
+
top: calc(100% + 6px);
|
|
156
|
+
left: 50%;
|
|
157
|
+
transform: translateX(-50%);
|
|
158
|
+
}
|
|
159
|
+
.dc-launcher[data-tip]:hover::after {
|
|
160
|
+
top: 50%;
|
|
161
|
+
right: calc(100% + 8px);
|
|
162
|
+
transform: translateY(-50%);
|
|
163
|
+
}
|
|
164
|
+
:host([data-position="top-left"]) .dc-launcher[data-tip]:hover::after,
|
|
165
|
+
:host([data-position="bottom-left"]) .dc-launcher[data-tip]:hover::after {
|
|
166
|
+
right: auto;
|
|
167
|
+
left: calc(100% + 8px);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ===== Filter chips ===== */
|
|
171
|
+
.dc-filters {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-wrap: wrap;
|
|
174
|
+
gap: 4px;
|
|
175
|
+
padding: 8px 16px;
|
|
176
|
+
background: var(--dc-surface);
|
|
177
|
+
border-bottom: 1px solid var(--dc-border);
|
|
178
|
+
flex-shrink: 0;
|
|
179
|
+
}
|
|
180
|
+
.dc-chip {
|
|
181
|
+
padding: 3px 12px;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
letter-spacing: 0.03em;
|
|
185
|
+
color: var(--dc-muted);
|
|
186
|
+
background: transparent;
|
|
187
|
+
border: 1px solid var(--dc-border);
|
|
188
|
+
border-radius: 999px;
|
|
189
|
+
cursor: pointer;
|
|
190
|
+
font-family: inherit;
|
|
191
|
+
transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
|
|
192
|
+
}
|
|
193
|
+
.dc-chip:hover { color: var(--dc-text); border-color: #4f77fb; }
|
|
194
|
+
.dc-chip--active { color: #fff; border-color: transparent; background: var(--dc-accent); }
|
|
195
|
+
|
|
196
|
+
/* ===== List ===== */
|
|
197
|
+
.dc-list {
|
|
198
|
+
flex: 1;
|
|
199
|
+
min-height: 0;
|
|
200
|
+
overflow-y: auto;
|
|
201
|
+
overflow-x: hidden;
|
|
202
|
+
padding: 4px 0;
|
|
203
|
+
scrollbar-width: thin;
|
|
204
|
+
scrollbar-color: var(--dc-border) transparent;
|
|
205
|
+
}
|
|
206
|
+
.dc-list::-webkit-scrollbar { width: 10px; }
|
|
207
|
+
.dc-list::-webkit-scrollbar-thumb { background: var(--dc-border); border-radius: 999px; }
|
|
208
|
+
.dc-list::-webkit-scrollbar-track { background: transparent; }
|
|
209
|
+
|
|
210
|
+
.dc-row {
|
|
211
|
+
display: grid;
|
|
212
|
+
grid-template-columns: auto auto minmax(0, 1fr);
|
|
213
|
+
gap: 8px;
|
|
214
|
+
align-items: baseline;
|
|
215
|
+
padding: 4px 16px;
|
|
216
|
+
border-left: 3px solid transparent;
|
|
217
|
+
font-size: 12px;
|
|
218
|
+
line-height: 1.5;
|
|
219
|
+
}
|
|
220
|
+
.dc-row:hover { background: var(--dc-row-hover); }
|
|
221
|
+
.dc-row--error { border-left-color: var(--dc-error); }
|
|
222
|
+
.dc-row--error .dc-tag, .dc-row--error .dc-text { color: var(--dc-error); }
|
|
223
|
+
.dc-row--warn { border-left-color: var(--dc-warn); }
|
|
224
|
+
.dc-row--warn .dc-tag { color: var(--dc-warn); }
|
|
225
|
+
.dc-row--info { border-left-color: var(--dc-info); }
|
|
226
|
+
.dc-row--info .dc-tag { color: var(--dc-info); }
|
|
227
|
+
.dc-row--log, .dc-row--debug { border-left-color: var(--dc-muted); }
|
|
228
|
+
.dc-row--log .dc-tag, .dc-row--debug .dc-tag { color: var(--dc-muted); }
|
|
229
|
+
|
|
230
|
+
.dc-time { color: var(--dc-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
231
|
+
.dc-tag { min-width: 42px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap; }
|
|
232
|
+
.dc-text { margin: 0; min-width: 0; font-family: inherit; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; color: var(--dc-text); }
|
|
233
|
+
|
|
234
|
+
.dc-empty { padding: 24px 16px; text-align: center; font-size: 13px; color: var(--dc-muted); }
|
|
235
|
+
|
|
236
|
+
@media (max-width: 600px) {
|
|
237
|
+
.dc-panel { max-height: 65vh; }
|
|
238
|
+
}
|
|
239
|
+
`;var b=[{value:"all",label:"All",match:()=>true},{value:"log",label:"Log",match:e=>e==="log"||e==="debug"},{value:"info",label:"Info",match:e=>e==="info"},{value:"warn",label:"Warn",match:e=>e==="warn"},{value:"error",label:"Error",match:e=>e==="error"}],s=(e,o=2)=>String(e).padStart(o,"0"),m=e=>`${s(e.getHours())}:${s(e.getMinutes())}:${s(e.getSeconds())}.${s(e.getMilliseconds(),3)}`,x=e=>`${e.getFullYear()}-${s(e.getMonth()+1)}-${s(e.getDate())} ${m(e)}`,w=e=>`${e.getFullYear()}${s(e.getMonth()+1)}${s(e.getDate())}-${s(e.getHours())}${s(e.getMinutes())}${s(e.getSeconds())}`,y=typeof HTMLElement<"u"?HTMLElement:class{},p=class extends y{constructor(){super();this.unsubscribe=null;this.isOpen=false;this.autoScroll=true;this.filter=b[0];this.root=this.attachShadow({mode:"open"});}connectedCallback(){this.render(),this.replayExisting(),this.unsubscribe=l.subscribe(t=>this.onLog(t));}disconnectedCallback(){this.unsubscribe?.(),this.unsubscribe=null;}show(){this.setOpen(true);}hide(){this.setOpen(false);}toggle(){this.setOpen(!this.isOpen);}render(){this.root.innerHTML=`
|
|
240
|
+
<style>${f}</style>
|
|
241
|
+
<button class="dc-launcher" type="button" part="launcher" data-tip="Show application logs" aria-label="Show application logs">
|
|
242
|
+
${c.terminal}
|
|
243
|
+
<span class="dc-badge" hidden></span>
|
|
244
|
+
</button>
|
|
245
|
+
<section class="dc-panel" part="panel" role="log" aria-live="polite" aria-label="Application logs">
|
|
246
|
+
<header class="dc-header">
|
|
247
|
+
<div class="dc-title">
|
|
248
|
+
<span class="dc-title-icon">${c.terminal}</span>
|
|
249
|
+
<span class="dc-title-text">Application Logs</span>
|
|
250
|
+
<span class="dc-count" hidden>0</span>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="dc-actions">
|
|
253
|
+
<button class="dc-action dc-action--active" type="button" data-act="autoscroll" data-tip="Auto-scroll: on (following newest)" aria-label="Toggle auto-scroll">${c.pin}</button>
|
|
254
|
+
<button class="dc-action" type="button" data-act="bottom" data-tip="Scroll to bottom" aria-label="Scroll to bottom">${c.chevronsDown}</button>
|
|
255
|
+
<button class="dc-action" type="button" data-act="download" data-tip="Download logs" aria-label="Download logs" disabled>${c.download}</button>
|
|
256
|
+
<button class="dc-action" type="button" data-act="clear" data-tip="Clear logs" aria-label="Clear logs" disabled>${c.trash}</button>
|
|
257
|
+
<button class="dc-action" type="button" data-act="close" data-tip="Close" aria-label="Close application logs">${c.close}</button>
|
|
258
|
+
</div>
|
|
259
|
+
</header>
|
|
260
|
+
<div class="dc-filters"></div>
|
|
261
|
+
<div class="dc-list"></div>
|
|
262
|
+
<div class="dc-empty">No log entries captured yet.</div>
|
|
263
|
+
</section>
|
|
264
|
+
`,this.launcherEl=this.query(".dc-launcher"),this.badgeEl=this.query(".dc-badge"),this.panelEl=this.query(".dc-panel"),this.countEl=this.query(".dc-count"),this.listEl=this.query(".dc-list"),this.emptyEl=this.query(".dc-empty"),this.autoScrollBtn=this.query('[data-act="autoscroll"]'),this.downloadBtn=this.query('[data-act="download"]'),this.clearBtn=this.query('[data-act="clear"]'),this.launcherEl.addEventListener("click",()=>this.setOpen(true)),this.query(".dc-actions").addEventListener("click",a=>{switch(a.target.closest(".dc-action")?.dataset.act){case "autoscroll":this.toggleAutoScroll();break;case "bottom":this.scrollToBottom();break;case "download":this.download();break;case "clear":l.clear();break;case "close":this.setOpen(false);break}});let t=this.query(".dc-filters");for(let a of b){let r=document.createElement("button");r.type="button",r.className="dc-chip"+(a===this.filter?" dc-chip--active":""),r.textContent=a.label,r.addEventListener("click",()=>this.setFilter(a,r)),t.appendChild(r);}}query(t){return this.root.querySelector(t)}replayExisting(){for(let t of l.entries)this.appendRow(t);this.refresh(),this.scrollToBottomIfFollowing();}onLog(t){t.type==="clear"?this.listEl.replaceChildren():this.appendRow(t.entry),this.refresh(),t.type==="add"&&this.scrollToBottomIfFollowing();}appendRow(t){let a=document.createElement("div");a.className=`dc-row dc-row--${t.level}`,a.dataset.level=t.level,a.hidden=!this.filter.match(t.level);let r=document.createElement("span");r.className="dc-time",r.textContent=m(t.time);let i=document.createElement("span");i.className="dc-tag",i.textContent=t.level;let n=document.createElement("pre");for(n.className="dc-text",n.textContent=t.text,a.append(r,i,n),this.listEl.appendChild(a);this.listEl.childElementCount>l.limit;)this.listEl.firstElementChild?.remove();}refresh(){let t=0,a=0;for(let n of l.entries)n.level==="error"?t++:n.level==="warn"&&a++;let r=l.entries.length;this.countEl.textContent=String(r),this.countEl.hidden=r===0;let i=t+a;this.badgeEl.hidden=this.isOpen||i===0,this.badgeEl.textContent=String(i),this.badgeEl.className="dc-badge "+(t>0?"dc-badge--error":"dc-badge--warn"),this.downloadBtn.disabled=r===0,this.clearBtn.disabled=r===0,this.emptyEl.hidden=!!this.listEl.querySelector(".dc-row:not([hidden])");}setOpen(t){this.isOpen=t,this.panelEl.classList.toggle("dc-panel--open",t),this.launcherEl.hidden=t,this.refresh(),t&&this.scrollToBottomIfFollowing();}toggleAutoScroll(){this.autoScroll=!this.autoScroll,this.autoScrollBtn.classList.toggle("dc-action--active",this.autoScroll),this.autoScrollBtn.dataset.tip=this.autoScroll?"Auto-scroll: on (following newest)":"Auto-scroll: off",this.scrollToBottomIfFollowing();}setFilter(t,a){this.filter=t,this.root.querySelectorAll(".dc-chip").forEach(r=>r.classList.toggle("dc-chip--active",r===a)),this.listEl.querySelectorAll(".dc-row").forEach(r=>{r.hidden=!t.match(r.dataset.level);}),this.refresh(),this.scrollToBottomIfFollowing();}scrollToBottom(){this.listEl.scrollTop=this.listEl.scrollHeight;}scrollToBottomIfFollowing(){!this.isOpen||!this.autoScroll||requestAnimationFrame(()=>{this.listEl.scrollTop=this.listEl.scrollHeight;});}download(){let t=l.entries;if(t.length===0)return;let a=t.map(h=>`[${x(h.time)}] ${h.level.toUpperCase()} ${h.text}`).join(`
|
|
265
|
+
`),r=new Blob([a],{type:"text/plain;charset=utf-8"}),i=URL.createObjectURL(r),n=document.createElement("a");n.href=i,n.download=`application-logs-${w(new Date)}.log`,document.body.appendChild(n),n.click(),n.remove(),URL.revokeObjectURL(i);}};var g="debug-console";function E(e=g){typeof customElements>"u"||customElements.get(e)||customElements.define(e,p);}function L(e={}){l.init({max:e.max,captureGlobalErrors:e.captureGlobalErrors});}function O(e={}){if(e.enabled===false||typeof document>"u")return null;L(e),E();let o=document.querySelector(g),t=o??document.createElement(g);if(!o){e.position&&t.setAttribute("data-position",e.position),e.accent&&t.style.setProperty("--dc-accent",e.accent);let a=()=>{document.body.appendChild(t),e.open&&t.show();};document.body?a():window.addEventListener("DOMContentLoaded",a,{once:true});}return {element:t,show:()=>t.show(),hide:()=>t.hide(),toggle:()=>t.toggle(),clear:()=>l.clear(),destroy:()=>t.remove()}}exports.DebugConsoleElement=p;exports.ELEMENT_NAME=g;exports.defineDebugConsole=E;exports.initDebugConsole=O;exports.logger=l;exports.startCapture=L;//# sourceMappingURL=index.cjs.map
|
|
266
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/icons.ts","../src/logger.ts","../src/styles.ts","../src/console-element.ts","../src/index.ts"],"names":["svg","paths","icons","LEVELS","LoggerCore","options","listener","con","level","original","args","event","text","entry","arg","value","seen","_key","val","logger","STYLES","FILTERS","pad","n","len","fmtTime","d","fmtDateTime","fmtFileStamp","HTMLElementBase","DebugConsoleElement","filtersEl","def","chip","selector","row","time","tag","errors","warnings","total","badge","open","c","entries","content","e","blob","url","link","ELEMENT_NAME","defineDebugConsole","name","startCapture","initDebugConsole","existing","element","mount"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Inline, line-style SVG icons (currentColor-driven, no icon-font dependency).\n * 24Γ24 viewBox, 2px stroke β resolution-independent and themeable via `color`.\n */\nconst svg = (paths: string): string =>\n `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\">${paths}</svg>`;\n\nexport const icons = {\n /** Terminal window with a prompt β launcher + panel title. */\n terminal: svg('<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"m7 9 3 3-3 3\"/><path d=\"M13 15h4\"/>'),\n /** Pin β \"pinned to the newest line\" (auto-scroll on). */\n pin: svg('<path d=\"M12 17v5\"/><path d=\"M9 10.8V6a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8\"/><path d=\"M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.2V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.8a2 2 0 0 0-1.1-1.7l-1.8-.9A2 2 0 0 1 15 10.8V6a1 1 0 0 0-1-1\"/>'),\n /** Double chevron down β jump to latest. */\n chevronsDown: svg('<path d=\"m7 6 5 5 5-5\"/><path d=\"m7 13 5 5 5-5\"/>'),\n /** Tray with down arrow β download. */\n download: svg('<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><path d=\"m7 10 5 5 5-5\"/><path d=\"M12 15V3\"/>'),\n /** Trash β clear. */\n trash: svg('<path d=\"M3 6h18\"/><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><path d=\"m19 6-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/>'),\n /** X β close. */\n close: svg('<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/>')\n} as const;\n","import type { LogEntry, LogLevel } from './types';\n\n/** Emitted to subscribers whenever the buffer changes. */\nexport type LogEvent = { type: 'add'; entry: LogEntry } | { type: 'clear' };\n\ntype LogListener = (event: LogEvent) => void;\n\nconst LEVELS: readonly LogLevel[] = ['log', 'info', 'warn', 'error', 'debug'];\n\n/**\n * Framework-agnostic capture core. Patches the global `console`, buffers entries\n * (bounded, oldest dropped), captures uncaught errors, and notifies subscribers.\n * A single shared instance is exported as {@link logger}.\n *\n * Patched methods always call the original first, so DevTools keeps working β\n * this MIRRORS console output, it never swallows it.\n */\nclass LoggerCore {\n private readonly _entries: LogEntry[] = [];\n private readonly listeners = new Set<LogListener>();\n private nextId = 0;\n private max = 500;\n private patched = false;\n private errorsHooked = false;\n\n /** The current buffer, oldest β newest (read-only). */\n get entries(): readonly LogEntry[] {\n return this._entries;\n }\n\n /** The configured buffer cap. */\n get limit(): number {\n return this.max;\n }\n\n /** Patch the console and (optionally) global error handlers. Idempotent. */\n init(options: { max?: number; captureGlobalErrors?: boolean } = {}): void {\n if (typeof options.max === 'number' && options.max > 0) {\n this.max = Math.floor(options.max);\n }\n this.patchConsole();\n if (options.captureGlobalErrors !== false) {\n this.captureGlobalErrors();\n }\n }\n\n /** Subscribe to buffer changes. Returns an unsubscribe function. */\n subscribe(listener: LogListener): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Empty the buffer (does not unpatch the console). */\n clear(): void {\n this._entries.length = 0;\n this.emit({ type: 'clear' });\n }\n\n private patchConsole(): void {\n if (this.patched || typeof console === 'undefined') {\n return;\n }\n this.patched = true;\n\n // Cast to a writable record of only the levels we patch; every other native\n // console method is left untouched.\n const con = console as unknown as Record<LogLevel, (...args: unknown[]) => void>;\n for (const level of LEVELS) {\n const original = con[level].bind(console);\n con[level] = (...args: unknown[]): void => {\n original(...args); // mirror β forward to the real console first\n this.push(level, this.stringifyArgs(args));\n };\n }\n }\n\n private captureGlobalErrors(): void {\n if (this.errorsHooked || typeof window === 'undefined') {\n return;\n }\n this.errorsHooked = true;\n\n window.addEventListener('error', (event: ErrorEvent): void => {\n const text = event.error != null ? this.stringifyValue(event.error) : event.message || 'Uncaught error';\n this.push('error', text);\n });\n\n window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent): void => {\n this.push('error', `Unhandled promise rejection: ${this.stringifyValue(event.reason)}`);\n });\n }\n\n private push(level: LogLevel, text: string): void {\n const entry: LogEntry = { id: this.nextId++, level, time: new Date(), text };\n this._entries.push(entry);\n if (this._entries.length > this.max) {\n this._entries.splice(0, this._entries.length - this.max);\n }\n this.emit({ type: 'add', entry });\n }\n\n private emit(event: LogEvent): void {\n // A subscriber error must never break the patched console it was triggered from.\n this.listeners.forEach(listener => {\n try {\n listener(event);\n } catch {\n /* swallow β the mirror must not affect the app's own console call */\n }\n });\n }\n\n private stringifyArgs(args: unknown[]): string {\n return args.map(arg => this.stringifyValue(arg)).join(' ');\n }\n\n private stringifyValue(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n if (value instanceof Error) {\n return `${value.name}: ${value.message}`;\n }\n\n try {\n const seen = new WeakSet<object>();\n const json = JSON.stringify(\n value,\n (_key: string, val: unknown) => {\n if (typeof val === 'bigint') {\n return val.toString();\n }\n if (typeof val === 'object' && val !== null) {\n if (seen.has(val)) {\n return '[Circular]';\n }\n seen.add(val);\n }\n return val;\n },\n 2\n );\n return json ?? String(value);\n } catch {\n return String(value);\n }\n }\n}\n\n/** The shared capture core (console is global, so there is exactly one). */\nexport const logger = new LoggerCore();\n","/**\n * Shadow-DOM stylesheet for the debug console. Self-contained fixed dark\n * \"terminal\" palette (theme-independent), with a handful of `--dc-*` custom\n * properties consumers can override (e.g. `--dc-accent`). Because it lives in a\n * shadow root, none of these rules leak into or out of the host application.\n */\nexport const STYLES = `\n:host {\n --dc-bg: #0b0e14;\n --dc-surface: #11151f;\n --dc-border: #232a3a;\n --dc-text: #c9d1d9;\n --dc-muted: #8b949e;\n --dc-row-hover: rgba(255, 255, 255, 0.04);\n --dc-shadow: 0 10px 34px rgba(0, 0, 0, 0.5);\n --dc-accent: linear-gradient(135deg, #57bdff, #4f77fb, #3a5fcc);\n --dc-error: #ff6b6b;\n --dc-warn: #ffc107;\n --dc-info: #57bdff;\n --dc-badge-error: #dc3545;\n --dc-badge-warn: #ffc107;\n font-family: 'Cascadia Code', 'Fira Code', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\n[hidden] { display: none !important; }\n\n/* ===== Launcher ===== */\n.dc-launcher {\n position: fixed;\n top: 16px;\n right: 16px;\n z-index: 2147483000;\n width: 44px;\n height: 44px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 50%;\n background: var(--dc-accent);\n color: #fff;\n cursor: pointer;\n box-shadow: var(--dc-shadow);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n.dc-launcher svg { width: 22px; height: 22px; }\n.dc-launcher:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(79, 119, 251, 0.45); }\n:host([data-position=\"top-left\"]) .dc-launcher { top: 16px; left: 16px; right: auto; }\n:host([data-position=\"bottom-right\"]) .dc-launcher { top: auto; bottom: 16px; right: 16px; }\n:host([data-position=\"bottom-left\"]) .dc-launcher { top: auto; bottom: 16px; left: 16px; right: auto; }\n\n/* ===== Badge ===== */\n.dc-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n border-radius: 999px;\n border: 2px solid var(--dc-bg);\n}\n.dc-badge--error { background: var(--dc-badge-error); color: #fff; }\n.dc-badge--warn { background: var(--dc-badge-warn); color: #000; }\n\n/* ===== Panel ===== */\n.dc-panel {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n z-index: 2147482000;\n display: flex;\n flex-direction: column;\n max-height: 50vh;\n background: var(--dc-bg);\n color: var(--dc-text);\n border-bottom: 1px solid var(--dc-border);\n box-shadow: var(--dc-shadow);\n transform: translateY(-100%);\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n.dc-panel--open { transform: translateY(0); }\n\n/* ===== Header ===== */\n.dc-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 16px;\n background: var(--dc-surface);\n border-bottom: 1px solid var(--dc-border);\n flex-shrink: 0;\n}\n.dc-title { display: flex; align-items: center; gap: 8px; min-width: 0; }\n.dc-title-icon { display: inline-flex; color: var(--dc-info); }\n.dc-title-icon svg { width: 20px; height: 20px; }\n.dc-title-text { font-size: 14px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap; color: var(--dc-text); }\n.dc-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 18px;\n padding: 0 6px;\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n color: #fff;\n background: var(--dc-accent);\n border-radius: 999px;\n}\n\n/* ===== Actions ===== */\n.dc-actions { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }\n.dc-action {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n color: var(--dc-muted);\n background: transparent;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n transition: color 0.2s ease, background-color 0.2s ease;\n}\n.dc-action svg { width: 18px; height: 18px; }\n.dc-action:hover:not(:disabled) { color: var(--dc-text); background: var(--dc-row-hover); }\n.dc-action:disabled { opacity: 0.35; cursor: default; }\n.dc-action--active { color: var(--dc-info); }\n\n/* ===== Tooltips (self-contained; no host overlay layer) ===== */\n.dc-action[data-tip]:hover::after,\n.dc-launcher[data-tip]:hover::after {\n content: attr(data-tip);\n position: absolute;\n padding: 4px 8px;\n border-radius: 6px;\n background: #000;\n color: #fff;\n font-size: 11px;\n font-family: system-ui, -apple-system, sans-serif;\n font-weight: 500;\n white-space: nowrap;\n pointer-events: none;\n z-index: 10;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n}\n.dc-action[data-tip]:hover::after {\n top: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n}\n.dc-launcher[data-tip]:hover::after {\n top: 50%;\n right: calc(100% + 8px);\n transform: translateY(-50%);\n}\n:host([data-position=\"top-left\"]) .dc-launcher[data-tip]:hover::after,\n:host([data-position=\"bottom-left\"]) .dc-launcher[data-tip]:hover::after {\n right: auto;\n left: calc(100% + 8px);\n}\n\n/* ===== Filter chips ===== */\n.dc-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n padding: 8px 16px;\n background: var(--dc-surface);\n border-bottom: 1px solid var(--dc-border);\n flex-shrink: 0;\n}\n.dc-chip {\n padding: 3px 12px;\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.03em;\n color: var(--dc-muted);\n background: transparent;\n border: 1px solid var(--dc-border);\n border-radius: 999px;\n cursor: pointer;\n font-family: inherit;\n transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;\n}\n.dc-chip:hover { color: var(--dc-text); border-color: #4f77fb; }\n.dc-chip--active { color: #fff; border-color: transparent; background: var(--dc-accent); }\n\n/* ===== List ===== */\n.dc-list {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 4px 0;\n scrollbar-width: thin;\n scrollbar-color: var(--dc-border) transparent;\n}\n.dc-list::-webkit-scrollbar { width: 10px; }\n.dc-list::-webkit-scrollbar-thumb { background: var(--dc-border); border-radius: 999px; }\n.dc-list::-webkit-scrollbar-track { background: transparent; }\n\n.dc-row {\n display: grid;\n grid-template-columns: auto auto minmax(0, 1fr);\n gap: 8px;\n align-items: baseline;\n padding: 4px 16px;\n border-left: 3px solid transparent;\n font-size: 12px;\n line-height: 1.5;\n}\n.dc-row:hover { background: var(--dc-row-hover); }\n.dc-row--error { border-left-color: var(--dc-error); }\n.dc-row--error .dc-tag, .dc-row--error .dc-text { color: var(--dc-error); }\n.dc-row--warn { border-left-color: var(--dc-warn); }\n.dc-row--warn .dc-tag { color: var(--dc-warn); }\n.dc-row--info { border-left-color: var(--dc-info); }\n.dc-row--info .dc-tag { color: var(--dc-info); }\n.dc-row--log, .dc-row--debug { border-left-color: var(--dc-muted); }\n.dc-row--log .dc-tag, .dc-row--debug .dc-tag { color: var(--dc-muted); }\n\n.dc-time { color: var(--dc-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }\n.dc-tag { min-width: 42px; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; white-space: nowrap; }\n.dc-text { margin: 0; min-width: 0; font-family: inherit; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; color: var(--dc-text); }\n\n.dc-empty { padding: 24px 16px; text-align: center; font-size: 13px; color: var(--dc-muted); }\n\n@media (max-width: 600px) {\n .dc-panel { max-height: 65vh; }\n}\n`;\n","import { icons } from './icons';\nimport { logger, type LogEvent } from './logger';\nimport { STYLES } from './styles';\nimport type { LogEntry, LogLevel } from './types';\n\ninterface FilterDef {\n value: string;\n label: string;\n match: (level: LogLevel) => boolean;\n}\n\nconst FILTERS: readonly FilterDef[] = [\n { value: 'all', label: 'All', match: () => true },\n { value: 'log', label: 'Log', match: level => level === 'log' || level === 'debug' },\n { value: 'info', label: 'Info', match: level => level === 'info' },\n { value: 'warn', label: 'Warn', match: level => level === 'warn' },\n { value: 'error', label: 'Error', match: level => level === 'error' }\n];\n\nconst pad = (n: number, len = 2): string => String(n).padStart(len, '0');\nconst fmtTime = (d: Date): string => `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;\nconst fmtDateTime = (d: Date): string => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${fmtTime(d)}`;\nconst fmtFileStamp = (d: Date): string =>\n `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;\n\n// SSR-safe base: in a non-browser runtime `HTMLElement` is undefined, so extend\n// a harmless stub. The DOM-touching methods only ever run in the browser.\nconst HTMLElementBase: typeof HTMLElement =\n typeof HTMLElement !== 'undefined' ? HTMLElement : (class {} as unknown as typeof HTMLElement);\n\n/**\n * `<debug-console>` custom element. Renders the launcher + panel inside a shadow\n * root (full style isolation) and mirrors {@link logger}'s buffer in real time.\n * Register it with `defineDebugConsole()` or mount it via `initDebugConsole()`.\n */\nexport class DebugConsoleElement extends HTMLElementBase {\n private readonly root: ShadowRoot;\n\n private launcherEl!: HTMLButtonElement;\n private badgeEl!: HTMLElement;\n private panelEl!: HTMLElement;\n private countEl!: HTMLElement;\n private listEl!: HTMLElement;\n private emptyEl!: HTMLElement;\n private autoScrollBtn!: HTMLButtonElement;\n private downloadBtn!: HTMLButtonElement;\n private clearBtn!: HTMLButtonElement;\n\n private unsubscribe: (() => void) | null = null;\n private isOpen = false;\n private autoScroll = true;\n private filter: FilterDef = FILTERS[0];\n\n constructor() {\n super();\n this.root = this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback(): void {\n this.render();\n this.replayExisting();\n this.unsubscribe = logger.subscribe(event => this.onLog(event));\n }\n\n disconnectedCallback(): void {\n this.unsubscribe?.();\n this.unsubscribe = null;\n }\n\n /** Open the panel. */\n show(): void {\n this.setOpen(true);\n }\n\n /** Close the panel. */\n hide(): void {\n this.setOpen(false);\n }\n\n /** Toggle the panel. */\n toggle(): void {\n this.setOpen(!this.isOpen);\n }\n\n private render(): void {\n this.root.innerHTML = `\n <style>${STYLES}</style>\n <button class=\"dc-launcher\" type=\"button\" part=\"launcher\" data-tip=\"Show application logs\" aria-label=\"Show application logs\">\n ${icons.terminal}\n <span class=\"dc-badge\" hidden></span>\n </button>\n <section class=\"dc-panel\" part=\"panel\" role=\"log\" aria-live=\"polite\" aria-label=\"Application logs\">\n <header class=\"dc-header\">\n <div class=\"dc-title\">\n <span class=\"dc-title-icon\">${icons.terminal}</span>\n <span class=\"dc-title-text\">Application Logs</span>\n <span class=\"dc-count\" hidden>0</span>\n </div>\n <div class=\"dc-actions\">\n <button class=\"dc-action dc-action--active\" type=\"button\" data-act=\"autoscroll\" data-tip=\"Auto-scroll: on (following newest)\" aria-label=\"Toggle auto-scroll\">${icons.pin}</button>\n <button class=\"dc-action\" type=\"button\" data-act=\"bottom\" data-tip=\"Scroll to bottom\" aria-label=\"Scroll to bottom\">${icons.chevronsDown}</button>\n <button class=\"dc-action\" type=\"button\" data-act=\"download\" data-tip=\"Download logs\" aria-label=\"Download logs\" disabled>${icons.download}</button>\n <button class=\"dc-action\" type=\"button\" data-act=\"clear\" data-tip=\"Clear logs\" aria-label=\"Clear logs\" disabled>${icons.trash}</button>\n <button class=\"dc-action\" type=\"button\" data-act=\"close\" data-tip=\"Close\" aria-label=\"Close application logs\">${icons.close}</button>\n </div>\n </header>\n <div class=\"dc-filters\"></div>\n <div class=\"dc-list\"></div>\n <div class=\"dc-empty\">No log entries captured yet.</div>\n </section>\n `;\n\n this.launcherEl = this.query('.dc-launcher');\n this.badgeEl = this.query('.dc-badge');\n this.panelEl = this.query('.dc-panel');\n this.countEl = this.query('.dc-count');\n this.listEl = this.query('.dc-list');\n this.emptyEl = this.query('.dc-empty');\n this.autoScrollBtn = this.query('[data-act=\"autoscroll\"]');\n this.downloadBtn = this.query('[data-act=\"download\"]');\n this.clearBtn = this.query('[data-act=\"clear\"]');\n\n this.launcherEl.addEventListener('click', () => this.setOpen(true));\n\n this.query('.dc-actions').addEventListener('click', (event: Event) => {\n const btn = (event.target as HTMLElement).closest('.dc-action') as HTMLElement | null;\n switch (btn?.dataset['act']) {\n case 'autoscroll':\n this.toggleAutoScroll();\n break;\n case 'bottom':\n this.scrollToBottom();\n break;\n case 'download':\n this.download();\n break;\n case 'clear':\n logger.clear();\n break;\n case 'close':\n this.setOpen(false);\n break;\n }\n });\n\n const filtersEl = this.query('.dc-filters');\n for (const def of FILTERS) {\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.className = 'dc-chip' + (def === this.filter ? ' dc-chip--active' : '');\n chip.textContent = def.label;\n chip.addEventListener('click', () => this.setFilter(def, chip));\n filtersEl.appendChild(chip);\n }\n }\n\n private query<T extends Element>(selector: string): T {\n return this.root.querySelector(selector) as T;\n }\n\n private replayExisting(): void {\n for (const entry of logger.entries) {\n this.appendRow(entry);\n }\n this.refresh();\n this.scrollToBottomIfFollowing();\n }\n\n private onLog(event: LogEvent): void {\n if (event.type === 'clear') {\n this.listEl.replaceChildren();\n } else {\n this.appendRow(event.entry);\n }\n this.refresh();\n if (event.type === 'add') {\n this.scrollToBottomIfFollowing();\n }\n }\n\n /** Build one row (XSS-safe via textContent) and trim the DOM to the buffer cap. */\n private appendRow(entry: LogEntry): void {\n const row = document.createElement('div');\n row.className = `dc-row dc-row--${entry.level}`;\n row.dataset['level'] = entry.level;\n row.hidden = !this.filter.match(entry.level);\n\n const time = document.createElement('span');\n time.className = 'dc-time';\n time.textContent = fmtTime(entry.time);\n\n const tag = document.createElement('span');\n tag.className = 'dc-tag';\n tag.textContent = entry.level;\n\n const text = document.createElement('pre');\n text.className = 'dc-text';\n text.textContent = entry.text;\n\n row.append(time, tag, text);\n this.listEl.appendChild(row);\n\n while (this.listEl.childElementCount > logger.limit) {\n this.listEl.firstElementChild?.remove();\n }\n }\n\n /** Recompute counts, badge, count pill, disabled states and the empty message. */\n private refresh(): void {\n let errors = 0;\n let warnings = 0;\n for (const entry of logger.entries) {\n if (entry.level === 'error') {\n errors++;\n } else if (entry.level === 'warn') {\n warnings++;\n }\n }\n const total = logger.entries.length;\n\n this.countEl.textContent = String(total);\n this.countEl.hidden = total === 0;\n\n const badge = errors + warnings;\n this.badgeEl.hidden = this.isOpen || badge === 0;\n this.badgeEl.textContent = String(badge);\n this.badgeEl.className = 'dc-badge ' + (errors > 0 ? 'dc-badge--error' : 'dc-badge--warn');\n\n this.downloadBtn.disabled = total === 0;\n this.clearBtn.disabled = total === 0;\n\n this.emptyEl.hidden = !!this.listEl.querySelector('.dc-row:not([hidden])');\n }\n\n private setOpen(open: boolean): void {\n this.isOpen = open;\n this.panelEl.classList.toggle('dc-panel--open', open);\n this.launcherEl.hidden = open;\n this.refresh();\n if (open) {\n this.scrollToBottomIfFollowing();\n }\n }\n\n private toggleAutoScroll(): void {\n this.autoScroll = !this.autoScroll;\n this.autoScrollBtn.classList.toggle('dc-action--active', this.autoScroll);\n this.autoScrollBtn.dataset['tip'] = this.autoScroll ? 'Auto-scroll: on (following newest)' : 'Auto-scroll: off';\n this.scrollToBottomIfFollowing();\n }\n\n private setFilter(def: FilterDef, chip: HTMLElement): void {\n this.filter = def;\n this.root.querySelectorAll('.dc-chip').forEach(c => c.classList.toggle('dc-chip--active', c === chip));\n this.listEl.querySelectorAll<HTMLElement>('.dc-row').forEach(row => {\n row.hidden = !def.match(row.dataset['level'] as LogLevel);\n });\n this.refresh();\n this.scrollToBottomIfFollowing();\n }\n\n private scrollToBottom(): void {\n this.listEl.scrollTop = this.listEl.scrollHeight;\n }\n\n private scrollToBottomIfFollowing(): void {\n if (!this.isOpen || !this.autoScroll) {\n return;\n }\n requestAnimationFrame(() => {\n this.listEl.scrollTop = this.listEl.scrollHeight;\n });\n }\n\n private download(): void {\n const entries = logger.entries;\n if (entries.length === 0) {\n return;\n }\n const content = entries.map(e => `[${fmtDateTime(e.time)}] ${e.level.toUpperCase()} ${e.text}`).join('\\n');\n const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `application-logs-${fmtFileStamp(new Date())}.log`;\n document.body.appendChild(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n }\n}\n","import { DebugConsoleElement } from './console-element';\nimport { logger } from './logger';\nimport type { DebugConsoleHandle, DebugConsoleOptions } from './types';\n\n/** The custom element tag name. */\nexport const ELEMENT_NAME = 'debug-console';\n\n/**\n * Register the `<debug-console>` custom element (idempotent, browser-only).\n * Call this if you want to place `<debug-console>` in your own markup/JSX/template\n * instead of using {@link initDebugConsole}. You still need to call\n * {@link startCapture} (or `initDebugConsole`) once to begin capturing.\n */\nexport function defineDebugConsole(name: string = ELEMENT_NAME): void {\n if (typeof customElements === 'undefined') {\n return;\n }\n if (!customElements.get(name)) {\n customElements.define(name, DebugConsoleElement);\n }\n}\n\n/**\n * Begin patching the console + capturing errors, without mounting any UI.\n * Useful when you render `<debug-console>` yourself. Idempotent.\n */\nexport function startCapture(options: Pick<DebugConsoleOptions, 'max' | 'captureGlobalErrors'> = {}): void {\n logger.init({ max: options.max, captureGlobalErrors: options.captureGlobalErrors });\n}\n\n/**\n * One-call setup: start capturing and mount the overlay at the document root.\n *\n * Returns a handle to control/destroy it, or `null` when disabled or when there\n * is no DOM (SSR). When `enabled` is `false` the console is NOT patched and no\n * element is mounted.\n */\nexport function initDebugConsole(options: DebugConsoleOptions = {}): DebugConsoleHandle | null {\n if (options.enabled === false || typeof document === 'undefined') {\n return null;\n }\n\n startCapture(options);\n defineDebugConsole();\n\n // Idempotent: reuse an already-mounted overlay (e.g. a second call, or dev HMR)\n // instead of stacking a second launcher/panel on top.\n const existing = document.querySelector(ELEMENT_NAME) as DebugConsoleElement | null;\n const element: DebugConsoleElement = existing ?? (document.createElement(ELEMENT_NAME) as DebugConsoleElement);\n\n if (!existing) {\n if (options.position) {\n element.setAttribute('data-position', options.position);\n }\n if (options.accent) {\n element.style.setProperty('--dc-accent', options.accent);\n }\n\n const mount = (): void => {\n document.body.appendChild(element);\n if (options.open) {\n element.show();\n }\n };\n if (document.body) {\n mount();\n } else {\n window.addEventListener('DOMContentLoaded', mount, { once: true });\n }\n }\n\n return {\n element,\n show: () => element.show(),\n hide: () => element.hide(),\n toggle: () => element.toggle(),\n clear: () => logger.clear(),\n destroy: () => element.remove()\n };\n}\n\nexport { logger } from './logger';\nexport { DebugConsoleElement } from './console-element';\nexport type { LogEvent } from './logger';\nexport type { DebugConsoleHandle, DebugConsoleOptions, LauncherPosition, LogEntry, LogLevel } from './types';\n"]}
|