@djangocfg/debuger 2.1.233 → 2.1.235

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.
@@ -7,7 +7,7 @@ import {
7
7
  TooltipContent,
8
8
  TooltipTrigger,
9
9
  } from '@djangocfg/ui-core/components';
10
- import { cn } from '@djangocfg/ui-core/lib';
10
+ import { cn, isDev } from '@djangocfg/ui-core/lib';
11
11
  import { useDebugMode } from '@djangocfg/monitor/client';
12
12
  import { useDebugStore } from './store/debugStore';
13
13
  import { useDebugShortcut } from './hooks/useDebugShortcut';
@@ -23,87 +23,86 @@ export interface DebugButtonProps {
23
23
  className?: string;
24
24
  /** Props forwarded to DebugPanel */
25
25
  panel?: Omit<DebugPanelProps, never>;
26
- /** Start unlocked (e.g. in dev playgrounds). Default: false */
27
- defaultUnlocked?: boolean;
28
- }
29
-
30
- // ============================================================================
31
- // Easter-egg: 5 clicks in 2 seconds → unlock
32
- // ============================================================================
33
-
34
- function useEasterEgg(onTriggered: () => void) {
35
- const clicks = useRef<number[]>([]);
36
-
37
- return useCallback(() => {
38
- const now = Date.now();
39
- clicks.current = [...clicks.current.filter((t) => now - t < 2000), now];
40
- if (clicks.current.length >= 5) {
41
- clicks.current = [];
42
- onTriggered();
43
- }
44
- }, [onTriggered]);
26
+ /** Explicitly disable. Default: undefined (auto — visible in dev, hidden in prod, ?debug=1 always works) */
27
+ enabled?: boolean;
45
28
  }
46
29
 
47
30
  // ============================================================================
48
31
  // DebugButton
32
+ //
33
+ // Visibility rules (simple):
34
+ // 1. enabled === false AND no ?debug=1 → hidden completely
35
+ // 2. isDev OR ?debug=1 (localStorage) → visible (unlocked)
36
+ // 3. prod without ?debug=1 → invisible click trap (easter egg: 5 clicks)
49
37
  // ============================================================================
50
38
 
51
- export function DebugButton({ className, panel = {}, defaultUnlocked = false }: DebugButtonProps) {
39
+ export function DebugButton({ className, panel = {}, enabled }: DebugButtonProps) {
40
+ // All hooks called unconditionally (React rules of hooks)
52
41
  const isOpen = useDebugStore((s) => s.isOpen);
53
42
  const isUnlocked = useDebugStore((s) => s.isUnlocked);
54
43
  const unlock = useDebugStore((s) => s.unlock);
55
44
  const toggle = useDebugStore((s) => s.toggle);
56
-
57
45
  const isDebugMode = useDebugMode();
58
-
59
- // Auto-unlock when defaultUnlocked or debug mode is active
60
- useEffect(() => {
61
- if (defaultUnlocked || isDebugMode) unlock('1');
62
- }, [defaultUnlocked, isDebugMode, unlock]);
63
46
  const errorCount = useDebugErrorCount();
64
-
65
- // Keyboard shortcut
66
47
  useDebugShortcut();
67
48
 
68
- // Hide Next.js dev indicator badge while panel is open.
69
- // Uses MutationObserver because <nextjs-portal> is mounted asynchronously after hydration.
49
+ // Should this button be visible (unlocked)?
50
+ const shouldUnlock = isDev || isDebugMode;
51
+
52
+ // eslint-disable-next-line no-console
53
+ if (typeof window !== 'undefined') console.info('[DebugButton]', { isDev, isDebugMode, shouldUnlock, isUnlocked, enabled });
54
+
55
+ // Auto-unlock in dev or when ?debug=1
70
56
  useEffect(() => {
71
- if (typeof document === 'undefined') return;
57
+ if (shouldUnlock) unlock('1');
58
+ }, [shouldUnlock, unlock]);
72
59
 
60
+ // Hide Next.js dev indicator while panel is open
61
+ useEffect(() => {
62
+ if (typeof document === 'undefined') return;
73
63
  const apply = () => {
74
64
  document.querySelectorAll<HTMLElement>('nextjs-portal').forEach((el) => {
75
65
  el.style.display = isOpen ? 'none' : '';
76
66
  });
77
67
  };
78
-
79
- apply(); // handle already-mounted elements
80
-
68
+ apply();
81
69
  const observer = new MutationObserver(apply);
82
70
  observer.observe(document.body, { childList: true });
83
-
84
71
  return () => observer.disconnect();
85
72
  }, [isOpen]);
86
73
 
87
- // Easter-egg: 5 clicks in 2s → unlock directly
88
- const handleEasterEggClick = useCallback(() => { unlock('1'); toggle(); }, [unlock, toggle]);
89
- const handleEasterEggTrigger = useEasterEgg(handleEasterEggClick);
74
+ // Easter egg: 5 clicks in 2s → unlock
75
+ const clicks = useRef<number[]>([]);
76
+ const handleEasterEgg = useCallback(() => {
77
+ const now = Date.now();
78
+ clicks.current = [...clicks.current.filter((t) => now - t < 2000), now];
79
+ if (clicks.current.length >= 5) {
80
+ clicks.current = [];
81
+ unlock('1');
82
+ toggle();
83
+ }
84
+ }, [unlock, toggle]);
85
+
86
+ // === Render decision (after all hooks) ===
90
87
 
91
- // Visibility guard: button only shown when explicitly unlocked (?debug=1 or easter egg)
92
- // Always render invisible click trap for easter-egg regardless of env
88
+ // Explicitly disabled AND no debug override don't render at all
89
+ if (enabled === false && !isDebugMode) return null;
90
+
91
+ // Not unlocked → invisible click trap for easter egg
93
92
  if (!isUnlocked) {
94
93
  return (
95
94
  <div
96
- className="fixed bottom-5 left-5 w-9 h-9 z-[99999] opacity-0"
97
- onClick={handleEasterEggTrigger}
95
+ className="fixed bottom-5 left-5 w-9 h-9 z-[99999] opacity-0 cursor-default"
96
+ onClick={handleEasterEgg}
98
97
  />
99
98
  );
100
99
  }
101
100
 
101
+ // Unlocked → visible debug button
102
102
  return (
103
103
  <>
104
- {/* Trigger button — hidden when panel is open */}
105
104
  {!isOpen && (
106
- <div className="fixed bottom-5 left-16 z-[99999]">
105
+ <div className={cn('fixed bottom-5 z-[99999]', isDev ? 'left-16' : 'left-5')}>
107
106
  <Tooltip>
108
107
  <TooltipTrigger asChild>
109
108
  <button
@@ -115,7 +114,7 @@ export function DebugButton({ className, panel = {}, defaultUnlocked = false }:
115
114
  'transition-all duration-150 hover:scale-105',
116
115
  'focus:outline-none focus-visible:ring-2 focus-visible:ring-white/50',
117
116
  errorCount > 0 && 'bg-red-600/90',
118
- className
117
+ className,
119
118
  )}
120
119
  onClick={toggle}
121
120
  >
@@ -131,7 +130,6 @@ export function DebugButton({ className, panel = {}, defaultUnlocked = false }:
131
130
  </Tooltip>
132
131
  </div>
133
132
  )}
134
-
135
133
  <DebugPanel {...panel} />
136
134
  </>
137
135
  );
@@ -12,7 +12,7 @@ import type { Logger, LogLevel } from './types';
12
12
  // Helpers
13
13
  // ============================================================================
14
14
 
15
- const isDev = process.env.NODE_ENV !== 'production';
15
+ import { isDev } from '@djangocfg/ui-core/lib';
16
16
  const isBrowser = typeof window !== 'undefined';
17
17
 
18
18
  function extractStack(data?: Record<string, unknown>): {
@@ -1 +0,0 @@
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"]}