@djangocfg/debuger 2.1.219

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +345 -0
  2. package/dist/chunk-ESIQODSI.mjs +115 -0
  3. package/dist/chunk-ESIQODSI.mjs.map +1 -0
  4. package/dist/chunk-PAWJFY3S.mjs +6 -0
  5. package/dist/chunk-PAWJFY3S.mjs.map +1 -0
  6. package/dist/chunk-YQE3KTBG.mjs +64 -0
  7. package/dist/chunk-YQE3KTBG.mjs.map +1 -0
  8. package/dist/customEmitter-BO-1IWxm.d.ts +57 -0
  9. package/dist/emitters/index.cjs +74 -0
  10. package/dist/emitters/index.cjs.map +1 -0
  11. package/dist/emitters/index.d.ts +33 -0
  12. package/dist/emitters/index.mjs +4 -0
  13. package/dist/emitters/index.mjs.map +1 -0
  14. package/dist/index.cjs +887 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.ts +162 -0
  17. package/dist/index.mjs +694 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/logger/index.cjs +123 -0
  20. package/dist/logger/index.cjs.map +1 -0
  21. package/dist/logger/index.d.ts +50 -0
  22. package/dist/logger/index.mjs +4 -0
  23. package/dist/logger/index.mjs.map +1 -0
  24. package/package.json +67 -0
  25. package/src/DebugButton.tsx +143 -0
  26. package/src/DebugPanel.tsx +171 -0
  27. package/src/bridges/index.ts +1 -0
  28. package/src/bridges/monitorBridge.ts +63 -0
  29. package/src/emitters/Emitter.ts +51 -0
  30. package/src/emitters/audioEmitter.ts +74 -0
  31. package/src/emitters/customEmitter.ts +42 -0
  32. package/src/emitters/index.ts +10 -0
  33. package/src/hooks/useAudioEventLog.ts +147 -0
  34. package/src/hooks/useCustomEventLog.ts +39 -0
  35. package/src/hooks/useDebugShortcut.ts +27 -0
  36. package/src/hooks/useStoreSnapshot.ts +70 -0
  37. package/src/index.ts +62 -0
  38. package/src/logger/index.ts +3 -0
  39. package/src/logger/logStore.ts +89 -0
  40. package/src/logger/logger.ts +95 -0
  41. package/src/logger/types.ts +39 -0
  42. package/src/panels/AudioDebugPanel.tsx +157 -0
  43. package/src/panels/LogsPanel.tsx +267 -0
  44. package/src/panels/StorePanel.tsx +71 -0
  45. package/src/store/debugStore.ts +42 -0
  46. package/src/styles/index.css +5 -0
package/README.md ADDED
@@ -0,0 +1,345 @@
1
+ # @djangocfg/debuger
2
+
3
+ A universal floating debug panel for Next.js App Router projects. Drop-in overlay with structured logs, audio/media engine tracing, generic store inspection, and custom event channels.
4
+
5
+ Works in **dev** unconditionally. In **production** the panel is hidden behind a keyless unlock (URL param `?debug=1` or 5-click easter egg).
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - **Monitor bridge** — auto-subscribes to `@djangocfg/monitor` store and forwards all captured events (JS errors, console, network, validation) into the Logs panel. Zero config — install both packages.
12
+ - **Logs panel** — virtualized log list from the built-in `useDebugLogStore`. Filter by level, component, free text. Export JSON. **No `@djangocfg/ui-core` dependency.**
13
+ - **Audio panel** — real-time audio/media engine event log. RAF-batched at 60 fps. Metrics: sync interval, seek rate, per-kind counters. `DEBUG_AUDIO` localStorage toggles. **Events buffered (200) so late-opening panel replays history.**
14
+ - **Store panel** — generic polling-based Zustand store viewer. Pass any `getState` fn; renders a collapsible JSON tree.
15
+ - **Custom tabs** — extend the panel with your own `CustomDebugTab[]` components.
16
+ - **Keyboard shortcut** — `Cmd+D` toggles the panel.
17
+ - **Production unlock** — `?debug=1` in URL (any value) or 5-click easter egg in bottom-left corner.
18
+ - **Zero-cost emitters** — `emit()` is a no-op when no listeners are registered. Safe to call in hot paths.
19
+ - **SSR-safe** — no browser APIs at import time.
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pnpm add @djangocfg/debuger
27
+ ```
28
+
29
+ Peer dependencies:
30
+
31
+ ```bash
32
+ pnpm add react react-dom zustand lucide-react @djangocfg/ui-core @djangocfg/ui-tools @tanstack/react-virtual
33
+ ```
34
+
35
+ Optional — for monitor bridge:
36
+
37
+ ```bash
38
+ pnpm add @djangocfg/monitor
39
+ ```
40
+
41
+ ### Tailwind v4
42
+
43
+ Add the styles import to your app's `globals.css` alongside other package styles:
44
+
45
+ ```css
46
+ @import "@djangocfg/ui-nextjs/styles";
47
+ @import "@djangocfg/layouts/styles";
48
+ @import "@djangocfg/ui-tools/styles";
49
+ @import "@djangocfg/debuger/styles"; /* ← add this */
50
+ @import "tailwindcss";
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Quick start
56
+
57
+ ```tsx
58
+ // app/layout.tsx (or any client boundary)
59
+ import { DebugButton } from '@djangocfg/debuger';
60
+
61
+ export default function RootLayout({ children }) {
62
+ return (
63
+ <html>
64
+ <body>
65
+ {children}
66
+ <DebugButton />
67
+ </body>
68
+ </html>
69
+ );
70
+ }
71
+ ```
72
+
73
+ `DebugButton` mounts the panel, registers `Cmd+D`, and handles URL/easter-egg unlock.
74
+
75
+ ---
76
+
77
+ ## Production unlock
78
+
79
+ No secret key required. Three ways to unlock in production:
80
+
81
+ 1. **URL param** — `?debug=1` (any non-empty value): panel opens and param is removed from URL.
82
+ 2. **Easter egg** — click the bottom-left corner 5 times within 2 seconds.
83
+ 3. **Keyboard** — `Cmd+D` works once unlocked via either method above.
84
+
85
+ ---
86
+
87
+ ## Logger
88
+
89
+ The package ships its own logger — **no `@djangocfg/ui-core` dependency required**. Logs appear in the Logs panel automatically.
90
+
91
+ ```ts
92
+ import { createDebugLogger, debugLog } from '@djangocfg/debuger/logger';
93
+
94
+ // Reusable logger for a module
95
+ const log = createDebugLogger('MyComponent');
96
+ log.info('Mounted');
97
+ log.warn('Something looks off', { value: 42 });
98
+ log.error('Failed', { err: 'timeout' });
99
+
100
+ // One-shot helper (no instance needed)
101
+ debugLog('AudioEngine', 'debug', 'engine created');
102
+ ```
103
+
104
+ `LogLevel`: `'debug' | 'info' | 'warn' | 'error' | 'success'`
105
+
106
+ Access the store directly if needed:
107
+
108
+ ```ts
109
+ import { useDebugLogStore } from '@djangocfg/debuger/logger';
110
+
111
+ useDebugLogStore.getState().clearLogs();
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Custom tabs
117
+
118
+ ```tsx
119
+ import { DebugButton, StorePanel } from '@djangocfg/debuger';
120
+ import { Database } from 'lucide-react';
121
+ import { useMyStore } from '@/stores/myStore';
122
+
123
+ const myTabs = [
124
+ {
125
+ id: 'my-store',
126
+ label: 'My Store',
127
+ icon: Database,
128
+ panel: ({ isActive }) => (
129
+ <StorePanel
130
+ label="My Store"
131
+ getState={() => useMyStore.getState()}
132
+ isActive={isActive}
133
+ />
134
+ ),
135
+ },
136
+ ];
137
+
138
+ <DebugButton panel={{ tabs: myTabs }} />
139
+ ```
140
+
141
+ ### Panel props
142
+
143
+ | Prop | Type | Default | Description |
144
+ |------|------|---------|-------------|
145
+ | `tabs` | `CustomDebugTab[]` | `[]` | Extra tabs appended after built-ins |
146
+ | `position` | `'bottom-left' \| 'bottom-right' \| 'top-left' \| 'top-right'` | `'bottom-left'` | Panel anchor |
147
+ | `defaultHeight` | `number` | `480` | Panel height in px |
148
+ | `defaultWidth` | `number` | `560` | Panel width in px |
149
+
150
+ ---
151
+
152
+ ## Monitor Bridge
153
+
154
+ When `@djangocfg/monitor` is installed, the debug panel automatically bridges its event store into the Logs panel. No configuration needed — `DebugPanel` installs the subscription on mount.
155
+
156
+ Events appear with component names like `monitor:JS_ERROR`, `monitor:NETWORK_ERROR`, `monitor:WARNING`, etc.
157
+
158
+ You can also install the bridge manually if you don't use `DebugPanel` directly:
159
+
160
+ ```ts
161
+ import { installMonitorBridge } from '@djangocfg/debuger';
162
+
163
+ // Call once at app startup
164
+ installMonitorBridge();
165
+ ```
166
+
167
+ `@djangocfg/monitor` is an **optional peer** — if it's not installed, the bridge silently skips.
168
+
169
+ ---
170
+
171
+ ## Emitters
172
+
173
+ Import from the tree-shakeable sub-entry (no React dependency):
174
+
175
+ ```ts
176
+ import { emitAudioEvent, emitDebugEvent } from '@djangocfg/debuger/emitters';
177
+ ```
178
+
179
+ ### Audio emitter
180
+
181
+ Wire into your audio/media engine:
182
+
183
+ ```ts
184
+ import { emitAudioEvent, hasAudioListeners } from '@djangocfg/debuger/emitters';
185
+
186
+ // Hot-path guard: zero-cost when panel is closed
187
+ if (hasAudioListeners()) {
188
+ emitAudioEvent({ kind: 'sync', ts: Date.now(), trackId: 'abc', msg: `pos=${pos.toFixed(3)}` });
189
+ }
190
+
191
+ // Lifecycle events — always emit (buffered for late subscribers)
192
+ emitAudioEvent({ kind: 'engine', ts: Date.now(), msg: 'engine created' });
193
+ ```
194
+
195
+ Events are **ring-buffered (200)** — opening the Audio panel replays recent history even if the engine started before the panel was opened.
196
+
197
+ **`AudioEventKind`**: `play` | `pause` | `seek` | `ended` | `sync` | `load` | `error` | `engine` | `custom`
198
+
199
+ **`AudioDebugEvent`** shape:
200
+
201
+ ```ts
202
+ interface AudioDebugEvent {
203
+ kind: AudioEventKind;
204
+ ts: number; // Date.now()
205
+ trackId?: string;
206
+ msg: string;
207
+ data?: Record<string, unknown>;
208
+ }
209
+ ```
210
+
211
+ ### Custom channel emitter
212
+
213
+ ```ts
214
+ import { emitDebugEvent } from '@djangocfg/debuger/emitters';
215
+
216
+ emitDebugEvent({
217
+ channel: 'pipeline', // matches your custom tab id
218
+ kind: 'slot:updated',
219
+ ts: Date.now(),
220
+ msg: 'Scene 3 slot updated',
221
+ data: { sceneId: '...', slot: 'image' },
222
+ });
223
+ ```
224
+
225
+ Use `useCustomEventLog(isActive, 'pipeline')` inside your custom tab to receive these events.
226
+
227
+ ---
228
+
229
+ ## Hooks
230
+
231
+ ### `useAudioEventLog(active)`
232
+
233
+ Subscribes to audio events. RAF-batched — no render storms at 60 fps. Replays buffered history on subscribe.
234
+
235
+ ```ts
236
+ const { events, clear, seekRate, syncIntervalMs, kindCounts } = useAudioEventLog(isActive);
237
+ ```
238
+
239
+ ### `useCustomEventLog(active, channel?)`
240
+
241
+ Ring-buffer (300 events) for custom channel events.
242
+
243
+ ```ts
244
+ const { events, clear } = useCustomEventLog(isActive, 'pipeline');
245
+ ```
246
+
247
+ ### `useStoreSnapshot(getState, intervalMs?, active?)`
248
+
249
+ Polling-based Zustand snapshot. Avoids reactive subscription issues with Zustand v5.
250
+
251
+ ```ts
252
+ const snap = useStoreSnapshot(() => useMyStore.getState(), 200, isActive);
253
+ ```
254
+
255
+ ### `useDebugShortcut(options?)`
256
+
257
+ Registers a keyboard shortcut to toggle the panel. Called automatically inside `DebugButton`.
258
+
259
+ ```ts
260
+ useDebugShortcut(); // default: Cmd+D (meta+d)
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Generic `Emitter<TEvents>` class
266
+
267
+ ```ts
268
+ import { Emitter } from '@djangocfg/debuger/emitters';
269
+
270
+ type MyEvents = {
271
+ 'user:login': { id: string };
272
+ 'page:view': { path: string };
273
+ };
274
+
275
+ export const myEmitter = new Emitter<MyEvents>();
276
+
277
+ const unsub = myEmitter.on('user:login', ({ id }) => console.log(id));
278
+ myEmitter.emit('user:login', { id: '123' });
279
+ unsub();
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Development playground
285
+
286
+ ```bash
287
+ make playground
288
+ # or
289
+ cd playground && pnpm dev
290
+ ```
291
+
292
+ The webpack config aliases `@djangocfg/debuger → src/` so no build step is needed during development.
293
+
294
+ ---
295
+
296
+ ## Build
297
+
298
+ ```bash
299
+ pnpm build # tsup — ESM + CJS + .d.ts
300
+ pnpm dev # tsup --watch
301
+ pnpm check # tsc --noEmit
302
+ ```
303
+
304
+ Three entries produced:
305
+
306
+ | Entry | Output | Notes |
307
+ |-------|--------|-------|
308
+ | `src/index.ts` | `dist/index.{mjs,cjs,d.ts}` | React components, `'use client'` |
309
+ | `src/emitters/index.ts` | `dist/emitters/index.{mjs,cjs,d.ts}` | Pure TS, no React, tree-shakeable |
310
+ | `src/logger/index.ts` | `dist/logger/index.{mjs,cjs,d.ts}` | Logger + store, no ui-core dep |
311
+
312
+ ---
313
+
314
+ ## Package structure
315
+
316
+ ```
317
+ src/
318
+ ├── DebugButton.tsx # Floating trigger button + unlock logic
319
+ ├── DebugPanel.tsx # Tab shell (Logs, Audio + custom tabs)
320
+ ├── index.ts # Main entry — all exports
321
+ ├── bridges/
322
+ │ ├── monitorBridge.ts # @djangocfg/monitor → logStore bridge (optional)
323
+ │ └── index.ts
324
+ ├── emitters/
325
+ │ ├── Emitter.ts # Generic typed emitter class
326
+ │ ├── audioEmitter.ts # Audio/media event singleton + ring-buffer
327
+ │ ├── customEmitter.ts # Custom channel event singleton
328
+ │ └── index.ts # Emitters sub-entry
329
+ ├── hooks/
330
+ │ ├── useAudioEventLog.ts # RAF-batched audio event log
331
+ │ ├── useCustomEventLog.ts # Custom channel event log
332
+ │ ├── useDebugShortcut.ts # Keyboard shortcut registration
333
+ │ └── useStoreSnapshot.ts # Polling Zustand snapshot
334
+ ├── logger/
335
+ │ ├── types.ts # LogEntry, LogLevel, Logger types
336
+ │ ├── logStore.ts # useDebugLogStore (Zustand, no ui-core)
337
+ │ ├── logger.ts # createDebugLogger, debugLog
338
+ │ └── index.ts # Logger sub-entry
339
+ ├── panels/
340
+ │ ├── AudioDebugPanel.tsx # Audio tab content
341
+ │ ├── LogsPanel.tsx # Logs tab (virtualized, uses own store)
342
+ │ └── StorePanel.tsx # Generic store viewer
343
+ └── store/
344
+ └── debugStore.ts # Zustand store: isOpen, tab, isUnlocked
345
+ ```
@@ -0,0 +1,115 @@
1
+ import { __name } from './chunk-PAWJFY3S.mjs';
2
+ import { create } from 'zustand';
3
+
4
+ var MAX_LOGS = 1e3;
5
+ var DEFAULT_FILTER = {
6
+ levels: ["debug", "info", "warn", "error", "success"]
7
+ };
8
+ var _counter = 0;
9
+ function generateId() {
10
+ return `log-${Date.now()}-${++_counter}`;
11
+ }
12
+ __name(generateId, "generateId");
13
+ function matchesFilter(entry, filter) {
14
+ if (!filter.levels.includes(entry.level)) return false;
15
+ if (filter.component) {
16
+ if (!entry.component.toLowerCase().includes(filter.component.toLowerCase())) return false;
17
+ }
18
+ if (filter.search) {
19
+ const s = filter.search.toLowerCase();
20
+ const inMsg = entry.message.toLowerCase().includes(s);
21
+ const inData = entry.data ? JSON.stringify(entry.data).toLowerCase().includes(s) : false;
22
+ if (!inMsg && !inData) return false;
23
+ }
24
+ return true;
25
+ }
26
+ __name(matchesFilter, "matchesFilter");
27
+ var useDebugLogStore = create((set, get) => ({
28
+ logs: [],
29
+ filter: DEFAULT_FILTER,
30
+ addLog: /* @__PURE__ */ __name((entry) => {
31
+ const newEntry = {
32
+ ...entry,
33
+ id: generateId(),
34
+ timestamp: /* @__PURE__ */ new Date()
35
+ };
36
+ set((state) => {
37
+ const next = [...state.logs, newEntry];
38
+ return { logs: next.length > MAX_LOGS ? next.slice(-MAX_LOGS) : next };
39
+ });
40
+ }, "addLog"),
41
+ clearLogs: /* @__PURE__ */ __name(() => set({ logs: [] }), "clearLogs"),
42
+ setFilter: /* @__PURE__ */ __name((filter) => set((state) => ({ filter: { ...state.filter, ...filter } })), "setFilter"),
43
+ getFilteredLogs: /* @__PURE__ */ __name(() => {
44
+ const { logs, filter } = get();
45
+ return logs.filter((e) => matchesFilter(e, filter));
46
+ }, "getFilteredLogs"),
47
+ exportLogs: /* @__PURE__ */ __name(() => JSON.stringify(get().logs, null, 2), "exportLogs")
48
+ }));
49
+ var useDebugFilteredLogs = /* @__PURE__ */ __name(() => {
50
+ const logs = useDebugLogStore((s) => s.logs);
51
+ const filter = useDebugLogStore((s) => s.filter);
52
+ return logs.filter((e) => matchesFilter(e, filter));
53
+ }, "useDebugFilteredLogs");
54
+ var useDebugLogCount = /* @__PURE__ */ __name(() => useDebugLogStore((s) => s.logs.length), "useDebugLogCount");
55
+ var useDebugErrorCount = /* @__PURE__ */ __name(() => useDebugLogStore((s) => s.logs.filter((l) => l.level === "error").length), "useDebugErrorCount");
56
+ var isBrowser = typeof window !== "undefined";
57
+ function extractStack(data) {
58
+ if (!data) return { cleanData: void 0, stack: void 0 };
59
+ const cleanData = { ...data };
60
+ let stack;
61
+ if (data.error instanceof Error) {
62
+ stack = data.error.stack;
63
+ cleanData.error = { name: data.error.name, message: data.error.message };
64
+ } else if (typeof data.error === "object" && data.error !== null) {
65
+ const e = data.error;
66
+ if (typeof e.stack === "string") stack = e.stack;
67
+ if (typeof e.message === "string") cleanData.error = e.message;
68
+ }
69
+ return { cleanData, stack };
70
+ }
71
+ __name(extractStack, "extractStack");
72
+ function createDebugLogger(component) {
73
+ const write = /* @__PURE__ */ __name((level, message, data) => {
74
+ const { cleanData, stack } = extractStack(data);
75
+ if (isBrowser) {
76
+ useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });
77
+ }
78
+ {
79
+ const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
80
+ const prefix = `[${component}]`;
81
+ if (cleanData) {
82
+ console[method](prefix, message, cleanData);
83
+ } else {
84
+ console[method](prefix, message);
85
+ }
86
+ }
87
+ }, "write");
88
+ return {
89
+ debug: /* @__PURE__ */ __name((msg, data) => write("debug", msg, data), "debug"),
90
+ info: /* @__PURE__ */ __name((msg, data) => write("info", msg, data), "info"),
91
+ warn: /* @__PURE__ */ __name((msg, data) => write("warn", msg, data), "warn"),
92
+ error: /* @__PURE__ */ __name((msg, data) => write("error", msg, data), "error"),
93
+ success: /* @__PURE__ */ __name((msg, data) => write("success", msg, data), "success")
94
+ };
95
+ }
96
+ __name(createDebugLogger, "createDebugLogger");
97
+ function debugLog(component, level, message, data) {
98
+ const { cleanData, stack } = extractStack(data);
99
+ if (isBrowser) {
100
+ useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });
101
+ }
102
+ {
103
+ const method = level === "error" ? "error" : level === "warn" ? "warn" : "log";
104
+ if (cleanData) {
105
+ console[method](`[${component}]`, message, cleanData);
106
+ } else {
107
+ console[method](`[${component}]`, message);
108
+ }
109
+ }
110
+ }
111
+ __name(debugLog, "debugLog");
112
+
113
+ export { createDebugLogger, debugLog, useDebugErrorCount, useDebugFilteredLogs, useDebugLogCount, useDebugLogStore };
114
+ //# sourceMappingURL=chunk-ESIQODSI.mjs.map
115
+ //# sourceMappingURL=chunk-ESIQODSI.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logger/logStore.ts","../src/logger/logger.ts"],"names":[],"mappings":"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","file":"chunk-ESIQODSI.mjs","sourcesContent":["'use client';\n\nimport { create } from 'zustand';\nimport type { LogStore, LogEntry, LogFilter } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MAX_LOGS = 1000;\n\nconst DEFAULT_FILTER: LogFilter = {\n levels: ['debug', 'info', 'warn', 'error', 'success'],\n};\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nlet _counter = 0;\n\nfunction generateId(): string {\n return `log-${Date.now()}-${++_counter}`;\n}\n\nfunction matchesFilter(entry: LogEntry, filter: LogFilter): boolean {\n if (!filter.levels.includes(entry.level)) return false;\n\n if (filter.component) {\n if (!entry.component.toLowerCase().includes(filter.component.toLowerCase())) return false;\n }\n\n if (filter.search) {\n const s = filter.search.toLowerCase();\n const inMsg = entry.message.toLowerCase().includes(s);\n const inData = entry.data ? JSON.stringify(entry.data).toLowerCase().includes(s) : false;\n if (!inMsg && !inData) return false;\n }\n\n return true;\n}\n\n// ============================================================================\n// Store\n// ============================================================================\n\nexport const useDebugLogStore = create<LogStore>((set, get) => ({\n logs: [],\n filter: DEFAULT_FILTER,\n\n addLog: (entry) => {\n const newEntry: LogEntry = {\n ...entry,\n id: generateId(),\n timestamp: new Date(),\n };\n set((state) => {\n const next = [...state.logs, newEntry];\n return { logs: next.length > MAX_LOGS ? next.slice(-MAX_LOGS) : next };\n });\n },\n\n clearLogs: () => set({ logs: [] }),\n\n setFilter: (filter) =>\n set((state) => ({ filter: { ...state.filter, ...filter } })),\n\n getFilteredLogs: () => {\n const { logs, filter } = get();\n return logs.filter((e) => matchesFilter(e, filter));\n },\n\n exportLogs: () => JSON.stringify(get().logs, null, 2),\n}));\n\n// ============================================================================\n// Selector hooks\n// ============================================================================\n\nexport const useDebugFilteredLogs = () => {\n const logs = useDebugLogStore((s) => s.logs);\n const filter = useDebugLogStore((s) => s.filter);\n return logs.filter((e) => matchesFilter(e, filter));\n};\n\nexport const useDebugLogCount = () => useDebugLogStore((s) => s.logs.length);\n\nexport const useDebugErrorCount = () =>\n useDebugLogStore((s) => s.logs.filter((l) => l.level === 'error').length);\n","/**\n * Debug Logger\n *\n * Standalone logger for @djangocfg/debuger — no ui-core dependency.\n * Writes to useDebugLogStore (shown in LogsPanel) and to console in dev.\n */\n\nimport { useDebugLogStore } from './logStore';\nimport type { Logger, LogLevel } from './types';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst isDev = process.env.NODE_ENV !== 'production';\nconst isBrowser = typeof window !== 'undefined';\n\nfunction extractStack(data?: Record<string, unknown>): {\n cleanData: Record<string, unknown> | undefined;\n stack: string | undefined;\n} {\n if (!data) return { cleanData: undefined, stack: undefined };\n\n const cleanData = { ...data };\n let stack: string | undefined;\n\n if (data.error instanceof Error) {\n stack = data.error.stack;\n cleanData.error = { name: data.error.name, message: data.error.message };\n } else if (typeof data.error === 'object' && data.error !== null) {\n const e = data.error as Record<string, unknown>;\n if (typeof e.stack === 'string') stack = e.stack;\n if (typeof e.message === 'string') cleanData.error = e.message;\n }\n\n return { cleanData, stack };\n}\n\n// ============================================================================\n// createLogger\n// ============================================================================\n\nexport function createDebugLogger(component: string): Logger {\n const write = (level: LogLevel, message: string, data?: Record<string, unknown>) => {\n const { cleanData, stack } = extractStack(data);\n\n if (isBrowser) {\n useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });\n }\n\n if (isDev) {\n const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';\n const prefix = `[${component}]`;\n if (cleanData) {\n console[method](prefix, message, cleanData);\n } else {\n console[method](prefix, message);\n }\n }\n };\n\n return {\n debug: (msg, data) => write('debug', msg, data),\n info: (msg, data) => write('info', msg, data),\n warn: (msg, data) => write('warn', msg, data),\n error: (msg, data) => write('error', msg, data),\n success: (msg, data) => write('success', msg, data),\n };\n}\n\n// ============================================================================\n// debugLog — one-shot helper (creates logger on each call, for non-hot paths)\n// ============================================================================\n\nexport function debugLog(\n component: string,\n level: LogLevel,\n message: string,\n data?: Record<string, unknown>,\n): void {\n const { cleanData, stack } = extractStack(data);\n\n if (isBrowser) {\n useDebugLogStore.getState().addLog({ level, component, message, data: cleanData, stack });\n }\n\n if (isDev) {\n const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log';\n if (cleanData) {\n console[method](`[${component}]`, message, cleanData);\n } else {\n console[method](`[${component}]`, message);\n }\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ export { __name };
5
+ //# sourceMappingURL=chunk-PAWJFY3S.mjs.map
6
+ //# sourceMappingURL=chunk-PAWJFY3S.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-PAWJFY3S.mjs"}
@@ -0,0 +1,64 @@
1
+ import { __name } from './chunk-PAWJFY3S.mjs';
2
+
3
+ // src/emitters/Emitter.ts
4
+ var _Emitter = class _Emitter {
5
+ constructor() {
6
+ this.listeners = /* @__PURE__ */ new Map();
7
+ }
8
+ /**
9
+ * Emit an event. No-op if no listeners are registered.
10
+ */
11
+ emit(event, data) {
12
+ const handlers = this.listeners.get(event);
13
+ if (!handlers || handlers.size === 0) return;
14
+ handlers.forEach((h) => h(data));
15
+ }
16
+ /**
17
+ * Subscribe to an event. Returns an unsubscribe function.
18
+ */
19
+ on(event, handler) {
20
+ if (!this.listeners.has(event)) {
21
+ this.listeners.set(event, /* @__PURE__ */ new Set());
22
+ }
23
+ this.listeners.get(event).add(handler);
24
+ return () => this.listeners.get(event)?.delete(handler);
25
+ }
26
+ /** True when at least one listener is registered for this event (hot-path guard) */
27
+ hasListeners(event) {
28
+ return (this.listeners.get(event)?.size ?? 0) > 0;
29
+ }
30
+ /** Remove all listeners (useful for testing / cleanup) */
31
+ clear() {
32
+ this.listeners.clear();
33
+ }
34
+ };
35
+ __name(_Emitter, "Emitter");
36
+ var Emitter = _Emitter;
37
+
38
+ // src/emitters/audioEmitter.ts
39
+ var _emitter = new Emitter();
40
+ var BUFFER_SIZE = 200;
41
+ var _buffer = [];
42
+ var emitAudioEvent = /* @__PURE__ */ __name((event) => {
43
+ if (_buffer.length >= BUFFER_SIZE) _buffer.shift();
44
+ _buffer.push(event);
45
+ _emitter.emit("event", event);
46
+ }, "emitAudioEvent");
47
+ var subscribeAudioEvents = /* @__PURE__ */ __name((fn) => {
48
+ for (const e of _buffer) fn(e);
49
+ return _emitter.on("event", fn);
50
+ }, "subscribeAudioEvents");
51
+ var clearAudioEventBuffer = /* @__PURE__ */ __name(() => {
52
+ _buffer.length = 0;
53
+ }, "clearAudioEventBuffer");
54
+ var hasAudioListeners = /* @__PURE__ */ __name(() => _emitter.hasListeners("event"), "hasAudioListeners");
55
+
56
+ // src/emitters/customEmitter.ts
57
+ var _emitter2 = new Emitter();
58
+ var emitDebugEvent = /* @__PURE__ */ __name((event) => _emitter2.emit("event", event), "emitDebugEvent");
59
+ var subscribeCustomEvents = /* @__PURE__ */ __name((fn) => _emitter2.on("event", fn), "subscribeCustomEvents");
60
+ var hasCustomListeners = /* @__PURE__ */ __name(() => _emitter2.hasListeners("event"), "hasCustomListeners");
61
+
62
+ export { Emitter, clearAudioEventBuffer, emitAudioEvent, emitDebugEvent, hasAudioListeners, hasCustomListeners, subscribeAudioEvents, subscribeCustomEvents };
63
+ //# sourceMappingURL=chunk-YQE3KTBG.mjs.map
64
+ //# sourceMappingURL=chunk-YQE3KTBG.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emitters/Emitter.ts","../src/emitters/audioEmitter.ts","../src/emitters/customEmitter.ts"],"names":["_emitter"],"mappings":";;;AAiBO,IAAM,QAAA,GAAN,MAAM,QAAA,CAAkC;AAAA,EAAxC,WAAA,GAAA;AACL,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAiD;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAKzE,IAAA,CAA8B,OAAU,IAAA,EAAwB;AAC9D,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AACzC,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,IAAA,KAAS,CAAA,EAAG;AACtC,IAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAC,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,EAAA,CAA4B,OAAU,OAAA,EAAiD;AACrF,IAAA,IAAI,CAAC,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAA,kBAAO,IAAI,KAAK,CAAA;AAAA,IACrC;AAEA,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,CAAG,IAAI,OAAc,CAAA;AAC7C,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,IAAI,KAAK,CAAA,EAAG,OAAO,OAAc,CAAA;AAAA,EAC/D;AAAA;AAAA,EAGA,aAAsC,KAAA,EAAmB;AACvD,IAAA,OAAA,CAAQ,KAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA,EAAG,QAAQ,CAAA,IAAK,CAAA;AAAA,EAClD;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,UAAU,KAAA,EAAM;AAAA,EACvB;AACF,CAAA;AAjC+C,MAAA,CAAA,QAAA,EAAA,SAAA,CAAA;AAAxC,IAAM,OAAA,GAAN;;;AC2BP,IAAM,QAAA,GAAW,IAAI,OAAA,EAAqB;AAG1C,IAAM,WAAA,GAAc,GAAA;AACpB,IAAM,UAA6B,EAAC;AAG7B,IAAM,cAAA,2BAAkB,KAAA,KAAiC;AAE9D,EAAA,IAAI,OAAA,CAAQ,MAAA,IAAU,WAAA,EAAa,OAAA,CAAQ,KAAA,EAAM;AACjD,EAAA,OAAA,CAAQ,KAAK,KAAK,CAAA;AAClB,EAAA,QAAA,CAAS,IAAA,CAAK,SAAS,KAAK,CAAA;AAC9B,CAAA,EAL8B,gBAAA;AAYvB,IAAM,oBAAA,2BAAwB,EAAA,KAAmD;AAEtF,EAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,EAAA,CAAG,CAAC,CAAA;AAC7B,EAAA,OAAO,QAAA,CAAS,EAAA,CAAG,OAAA,EAAS,EAAE,CAAA;AAChC,CAAA,EAJoC,sBAAA;AAO7B,IAAM,wCAAwB,MAAA,CAAA,MAAY;AAAE,EAAA,OAAA,CAAQ,MAAA,GAAS,CAAA;AAAG,CAAA,EAAlC,uBAAA;AAG9B,IAAM,iBAAA,mBAAoB,MAAA,CAAA,MAAe,QAAA,CAAS,YAAA,CAAa,OAAO,CAAA,EAA5C,mBAAA;;;AC1CjC,IAAMA,SAAAA,GAAW,IAAI,OAAA,EAAsB;AAGpC,IAAM,iCAAiB,MAAA,CAAA,CAAC,KAAA,KAC7BA,UAAS,IAAA,CAAK,OAAA,EAAS,KAAK,CAAA,EADA,gBAAA;AAIvB,IAAM,wCAAwB,MAAA,CAAA,CAAC,EAAA,KACpCA,UAAS,EAAA,CAAG,OAAA,EAAS,EAAE,CAAA,EADY,uBAAA;AAG9B,IAAM,kBAAA,mBAAqB,MAAA,CAAA,MAAeA,SAAAA,CAAS,YAAA,CAAa,OAAO,CAAA,EAA5C,oBAAA","file":"chunk-YQE3KTBG.mjs","sourcesContent":["/**\n * Generic typed event emitter\n *\n * Zero-cost: emit() is a no-op when no listeners are registered for an event.\n * SSR-safe: no browser APIs used.\n * No dependencies.\n *\n * Usage:\n * type MyEvents = { 'user:login': { id: string }; 'page:view': { path: string } };\n * export const myEmitter = new Emitter<MyEvents>();\n * myEmitter.on('user:login', ({ id }) => console.log(id));\n * myEmitter.emit('user:login', { id: '123' });\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EventMap = Record<string, any>;\n\nexport class Emitter<TEvents extends EventMap> {\n private listeners = new Map<keyof TEvents, Set<(data: unknown) => void>>();\n\n /**\n * Emit an event. No-op if no listeners are registered.\n */\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n const handlers = this.listeners.get(event);\n if (!handlers || handlers.size === 0) return;\n handlers.forEach((h) => h(data));\n }\n\n /**\n * Subscribe to an event. Returns an unsubscribe function.\n */\n on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.listeners.get(event)!.add(handler as any);\n return () => this.listeners.get(event)?.delete(handler as any);\n }\n\n /** True when at least one listener is registered for this event (hot-path guard) */\n hasListeners<K extends keyof TEvents>(event: K): boolean {\n return (this.listeners.get(event)?.size ?? 0) > 0;\n }\n\n /** Remove all listeners (useful for testing / cleanup) */\n clear(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Audio/Media Engine Event Emitter\n *\n * Universal zero-cost pub/sub for audio/media player debug events.\n * No React dependency — pure TypeScript module.\n *\n * Wire into your audio engine:\n * import { emitAudioEvent } from '@org/debuger/emitters';\n * emitAudioEvent({ kind: 'play', trackId: 'abc', ts: Date.now(), msg: 'started' });\n */\n\nimport { Emitter } from './Emitter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type AudioEventKind =\n | 'play'\n | 'pause'\n | 'seek'\n | 'ended'\n | 'sync' // periodic sync tick (RAF loop)\n | 'load'\n | 'error'\n | 'engine' // engine lifecycle: start/stop/create/destroy\n | 'custom';\n\nexport interface AudioDebugEvent {\n kind: AudioEventKind;\n ts: number;\n /** Track/player ID (short prefix) */\n trackId?: string;\n msg: string;\n /** Arbitrary metadata: positions, drift, etc. */\n data?: Record<string, unknown>;\n}\n\ntype AudioEvents = { event: AudioDebugEvent };\n\n// ============================================================================\n// Singleton emitter + ring-buffer\n// ============================================================================\n\nconst _emitter = new Emitter<AudioEvents>();\n\n/** Ring-buffer of recent events — replayed to new subscribers */\nconst BUFFER_SIZE = 200;\nconst _buffer: AudioDebugEvent[] = [];\n\n/** Emit an audio debug event. Zero-cost when no panel is open. */\nexport const emitAudioEvent = (event: AudioDebugEvent): void => {\n // Always buffer (capped) so late subscribers get recent history\n if (_buffer.length >= BUFFER_SIZE) _buffer.shift();\n _buffer.push(event);\n _emitter.emit('event', event);\n};\n\n/**\n * Subscribe to audio debug events.\n * Immediately replays buffered events to the new subscriber,\n * then delivers live events. Returns unsubscribe function.\n */\nexport const subscribeAudioEvents = (fn: (e: AudioDebugEvent) => void): (() => void) => {\n // Replay history so the panel sees events that happened before it opened\n for (const e of _buffer) fn(e);\n return _emitter.on('event', fn);\n};\n\n/** Clear the ring-buffer (called from the panel's Clear button) */\nexport const clearAudioEventBuffer = (): void => { _buffer.length = 0; };\n\n/** True when at least one listener is active (use as hot-path guard) */\nexport const hasAudioListeners = (): boolean => _emitter.hasListeners('event');\n","/**\n * Custom Debug Event Emitter\n *\n * Generic channel for consuming apps to send arbitrary debug events\n * without coupling to any specific domain.\n *\n * import { emitDebugEvent } from '@org/debuger/emitters';\n * emitDebugEvent({ channel: 'pipeline', kind: 'slot:updated', ts: Date.now(), msg: '...' });\n */\n\nimport { Emitter } from './Emitter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface CustomDebugEvent {\n /** Logical grouping — matches registered custom tab id */\n channel: string;\n kind: string;\n ts: number;\n msg: string;\n data?: Record<string, unknown>;\n}\n\ntype CustomEvents = { event: CustomDebugEvent };\n\n// ============================================================================\n// Singleton emitter\n// ============================================================================\n\nconst _emitter = new Emitter<CustomEvents>();\n\n/** Emit a custom debug event. Zero-cost when no panel is open. */\nexport const emitDebugEvent = (event: CustomDebugEvent): void =>\n _emitter.emit('event', event);\n\n/** Subscribe to custom debug events. Returns unsubscribe function. */\nexport const subscribeCustomEvents = (fn: (e: CustomDebugEvent) => void): (() => void) =>\n _emitter.on('event', fn);\n\nexport const hasCustomListeners = (): boolean => _emitter.hasListeners('event');\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Audio/Media Engine Event Emitter
3
+ *
4
+ * Universal zero-cost pub/sub for audio/media player debug events.
5
+ * No React dependency — pure TypeScript module.
6
+ *
7
+ * Wire into your audio engine:
8
+ * import { emitAudioEvent } from '@org/debuger/emitters';
9
+ * emitAudioEvent({ kind: 'play', trackId: 'abc', ts: Date.now(), msg: 'started' });
10
+ */
11
+ type AudioEventKind = 'play' | 'pause' | 'seek' | 'ended' | 'sync' | 'load' | 'error' | 'engine' | 'custom';
12
+ interface AudioDebugEvent {
13
+ kind: AudioEventKind;
14
+ ts: number;
15
+ /** Track/player ID (short prefix) */
16
+ trackId?: string;
17
+ msg: string;
18
+ /** Arbitrary metadata: positions, drift, etc. */
19
+ data?: Record<string, unknown>;
20
+ }
21
+ /** Emit an audio debug event. Zero-cost when no panel is open. */
22
+ declare const emitAudioEvent: (event: AudioDebugEvent) => void;
23
+ /**
24
+ * Subscribe to audio debug events.
25
+ * Immediately replays buffered events to the new subscriber,
26
+ * then delivers live events. Returns unsubscribe function.
27
+ */
28
+ declare const subscribeAudioEvents: (fn: (e: AudioDebugEvent) => void) => (() => void);
29
+ /** Clear the ring-buffer (called from the panel's Clear button) */
30
+ declare const clearAudioEventBuffer: () => void;
31
+ /** True when at least one listener is active (use as hot-path guard) */
32
+ declare const hasAudioListeners: () => boolean;
33
+
34
+ /**
35
+ * Custom Debug Event Emitter
36
+ *
37
+ * Generic channel for consuming apps to send arbitrary debug events
38
+ * without coupling to any specific domain.
39
+ *
40
+ * import { emitDebugEvent } from '@org/debuger/emitters';
41
+ * emitDebugEvent({ channel: 'pipeline', kind: 'slot:updated', ts: Date.now(), msg: '...' });
42
+ */
43
+ interface CustomDebugEvent {
44
+ /** Logical grouping — matches registered custom tab id */
45
+ channel: string;
46
+ kind: string;
47
+ ts: number;
48
+ msg: string;
49
+ data?: Record<string, unknown>;
50
+ }
51
+ /** Emit a custom debug event. Zero-cost when no panel is open. */
52
+ declare const emitDebugEvent: (event: CustomDebugEvent) => void;
53
+ /** Subscribe to custom debug events. Returns unsubscribe function. */
54
+ declare const subscribeCustomEvents: (fn: (e: CustomDebugEvent) => void) => (() => void);
55
+ declare const hasCustomListeners: () => boolean;
56
+
57
+ export { type AudioDebugEvent as A, type CustomDebugEvent as C, type AudioEventKind as a, subscribeCustomEvents as b, clearAudioEventBuffer as c, emitDebugEvent as d, emitAudioEvent as e, hasCustomListeners as f, hasAudioListeners as h, subscribeAudioEvents as s };