@djangocfg/debuger 2.1.234 → 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, isDev, isProd } 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,90 +23,86 @@ export interface DebugButtonProps {
23
23
  className?: string;
24
24
  /** Props forwarded to DebugPanel */
25
25
  panel?: Omit<DebugPanelProps, never>;
26
- /** Start unlocked. Default: true in development (process.env.NODE_ENV === 'development'), false otherwise */
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 = isDev }: 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();
47
+ useDebugShortcut();
64
48
 
65
- // Don't render in production unless explicitly unlocked via ?debug=1
66
- if (isProd && !defaultUnlocked && !isDebugMode) return null;
49
+ // Should this button be visible (unlocked)?
50
+ const shouldUnlock = isDev || isDebugMode;
67
51
 
68
- // Keyboard shortcut
69
- useDebugShortcut();
52
+ // eslint-disable-next-line no-console
53
+ if (typeof window !== 'undefined') console.info('[DebugButton]', { isDev, isDebugMode, shouldUnlock, isUnlocked, enabled });
70
54
 
71
- // Hide Next.js dev indicator badge while panel is open.
72
- // Uses MutationObserver because <nextjs-portal> is mounted asynchronously after hydration.
55
+ // Auto-unlock in dev or when ?debug=1
73
56
  useEffect(() => {
74
- if (typeof document === 'undefined') return;
57
+ if (shouldUnlock) unlock('1');
58
+ }, [shouldUnlock, unlock]);
75
59
 
60
+ // Hide Next.js dev indicator while panel is open
61
+ useEffect(() => {
62
+ if (typeof document === 'undefined') return;
76
63
  const apply = () => {
77
64
  document.querySelectorAll<HTMLElement>('nextjs-portal').forEach((el) => {
78
65
  el.style.display = isOpen ? 'none' : '';
79
66
  });
80
67
  };
81
-
82
- apply(); // handle already-mounted elements
83
-
68
+ apply();
84
69
  const observer = new MutationObserver(apply);
85
70
  observer.observe(document.body, { childList: true });
86
-
87
71
  return () => observer.disconnect();
88
72
  }, [isOpen]);
89
73
 
90
- // Easter-egg: 5 clicks in 2s → unlock directly
91
- const handleEasterEggClick = useCallback(() => { unlock('1'); toggle(); }, [unlock, toggle]);
92
- 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) ===
93
87
 
94
- // Visibility guard: button only shown when explicitly unlocked (?debug=1 or easter egg)
95
- // 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
96
92
  if (!isUnlocked) {
97
93
  return (
98
94
  <div
99
- className="fixed bottom-5 left-5 w-9 h-9 z-[99999] opacity-0"
100
- onClick={handleEasterEggTrigger}
95
+ className="fixed bottom-5 left-5 w-9 h-9 z-[99999] opacity-0 cursor-default"
96
+ onClick={handleEasterEgg}
101
97
  />
102
98
  );
103
99
  }
104
100
 
101
+ // Unlocked → visible debug button
105
102
  return (
106
103
  <>
107
- {/* Trigger button — hidden when panel is open */}
108
104
  {!isOpen && (
109
- <div className="fixed bottom-5 left-16 z-[99999]">
105
+ <div className={cn('fixed bottom-5 z-[99999]', isDev ? 'left-16' : 'left-5')}>
110
106
  <Tooltip>
111
107
  <TooltipTrigger asChild>
112
108
  <button
@@ -118,7 +114,7 @@ export function DebugButton({ className, panel = {}, defaultUnlocked = isDev }:
118
114
  'transition-all duration-150 hover:scale-105',
119
115
  'focus:outline-none focus-visible:ring-2 focus-visible:ring-white/50',
120
116
  errorCount > 0 && 'bg-red-600/90',
121
- className
117
+ className,
122
118
  )}
123
119
  onClick={toggle}
124
120
  >
@@ -134,7 +130,6 @@ export function DebugButton({ className, panel = {}, defaultUnlocked = isDev }:
134
130
  </Tooltip>
135
131
  </div>
136
132
  )}
137
-
138
133
  <DebugPanel {...panel} />
139
134
  </>
140
135
  );