feat: URL-driven debug/developer panel (à la amis devtools)#844
feat: URL-driven debug/developer panel (à la amis devtools)#844
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ok, and DebugPanel - @object-ui/core: export DebugCategory, DebugFlags, parseDebugFlags(), isDebugEnabled() with URL/env/globalThis support - @object-ui/react: add useDebugMode hook with URL detection, Ctrl+Shift+D shortcut, manual toggle - @object-ui/react: enhance SchemaRendererContext with debugFlags prop - @object-ui/components: add DebugPanel floating panel with Schema/Data/Registry/Flags tabs - Add comprehensive tests (32 new test cases across 3 test files) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 仔细评估是否完成任务要求的所有功能 |
…, SchemaRenderer debug attrs, MetadataInspector auto-open - @object-ui/core: add DebugCollector (perf/expr/event data collection, tree-shakeable singleton) - @object-ui/components: add Perf/Expr/Events tabs to DebugPanel (7 built-in tabs total) - @object-ui/react: SchemaRenderer injects data-debug-type/id attrs + reports render perf to DebugCollector when debug enabled - apps/console: MetadataInspector auto-opens when ?__debug URL param is present - Add 16 new tests (DebugCollector: 10, DebugPanel tabs: 3, SchemaRenderer debug: 3) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Completed all missing features in commit 1. URL-Driven Debug Mode ✅
2. Debug Panel Tabs (7/7) ✅
3. SchemaRenderer Enhancement ✅
4. Console MetadataInspector ✅
5. Code Decoupling ✅
Tests: 48 new tests across 5 files. All 512 react tests + 744 core tests pass. |
There was a problem hiding this comment.
Pull request overview
Introduces a URL-driven debug/developer mode across ObjectUI, enabling a floating DebugPanel UI and adding core utilities to parse debug flags and collect debug telemetry (perf/expr/events), with React runtime wiring to propagate flags and attach debug attributes.
Changes:
- Added
DebugFlagsparsing (parseDebugFlags) andDebugCollectorsingleton to@object-ui/core, plus enhancedisDebugEnabled()resolution order. - Added
useDebugMode()hook anddebugFlagspropagation viaSchemaRendererContext;SchemaRenderernow injectsdata-debug-*attributes and reports perf entries. - Added
DebugPanel(plugin-extensible tabs) to@object-ui/components, and updated ConsoleMetadataInspectorto auto-open when?__debugis present.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/hooks/useDebugMode.ts | New hook to derive debug state from URL, manual toggle, and keyboard shortcut. |
| packages/react/src/hooks/index.ts | Re-export useDebugMode. |
| packages/react/src/hooks/tests/useDebugMode.test.ts | Unit tests for useDebugMode. |
| packages/react/src/context/SchemaRendererContext.tsx | Adds optional debugFlags to renderer context/provider. |
| packages/react/src/tests/SchemaRenderer.debug.test.tsx | Tests for data-debug-* attribute injection behavior. |
| packages/react/src/SchemaRenderer.tsx | Injects debug attributes and reports render perf to DebugCollector. |
| packages/core/src/utils/debug.ts | Exports DebugCategory, adds DebugFlags, parseDebugFlags(), and enhanced isDebugEnabled(). |
| packages/core/src/utils/debug-collector.ts | New DebugCollector singleton collecting perf/expr/event entries with subscriptions and caps. |
| packages/core/src/utils/tests/debug.test.ts | Adds tests for parseDebugFlags() and isDebugEnabled(). |
| packages/core/src/utils/tests/debug-collector.test.ts | Adds tests for DebugCollector. |
| packages/core/src/index.ts | Re-exports debug-collector. |
| packages/components/src/index.ts | Re-exports debug components (./debug). |
| packages/components/src/debug/index.ts | New debug barrel export for DebugPanel. |
| packages/components/src/debug/tests/DebugPanel.test.tsx | Adds tests for DebugPanel rendering, tabs, accessibility, and empty states. |
| packages/components/src/debug/DebugPanel.tsx | New floating debug panel with built-in tabs and plugin extension points. |
| apps/console/src/components/MetadataInspector.tsx | Auto-opens MetadataInspector when ?__debug is detected. |
| ROADMAP.md | Marks URL-driven debug/developer panel as completed. |
| debugTime(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`); | ||
| const renderStart = isDebug ? performance.now() : 0; | ||
| const rendered = ( |
There was a problem hiding this comment.
performance.now() is used when isDebug is true, but performance is not guaranteed to exist in SSR/non-browser runtimes (even when debug is enabled via env/globalThis). This can throw and break rendering. Guard with typeof performance !== 'undefined' (or globalThis.performance?.now) and skip perf tracking / fall back when unavailable.
| debugLog, | ||
| debugTime, | ||
| debugTimeEnd, | ||
| DebugCollector, |
There was a problem hiding this comment.
DebugCollector is imported unconditionally in SchemaRenderer, which means the collector code is pulled into the main runtime bundle even when the DebugPanel is never imported. This contradicts the “tree-shakeable / only included when imported” goal in the PR description; consider restructuring so perf collection wiring is in a debug-only entrypoint or otherwise optional.
| DebugCollector, |
| function SchemaTab({ schema }: { schema?: unknown }) { | ||
| if (!schema) { | ||
| return <p className="text-xs text-muted-foreground italic">No schema available</p>; | ||
| } |
There was a problem hiding this comment.
SchemaTab treats any falsy schema as missing. If schema can ever legitimately be 0, false, or an empty string (e.g., debugging a primitive node), this will incorrectly show the empty state. Prefer checking schema == null (null/undefined) instead of !schema.
| } | ||
|
|
||
| function DataTab({ dataContext }: { dataContext?: unknown }) { | ||
| if (!dataContext) { |
There was a problem hiding this comment.
DataTab treats any falsy dataContext as missing. If the active data context can be 0, false, or '', the panel will incorrectly show the empty state. Prefer checking dataContext == null (null/undefined) instead of !dataContext.
| if (!dataContext) { | |
| if (dataContext == null) { |
| function RegistryTab() { | ||
| const entries = useMemo(() => { | ||
| try { | ||
| return ComponentRegistry.getAllTypes(); | ||
| } catch { | ||
| return []; | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
ComponentRegistry.getAllTypes() returns all internal registry keys, which include backward-compatibility fallback keys (e.g., both ui:button and button). This will show duplicates and inflate the “N components registered” count in the Registry tab. Consider de-duping (e.g., new Set(...)) or filtering out fallback keys / preferring namespaced keys for display.
| const urlFlags = useMemo(() => { | ||
| try { | ||
| return typeof window !== 'undefined' | ||
| ? parseDebugFlags(window.location.search) | ||
| : { enabled: false } as DebugFlags; | ||
| } catch { | ||
| return { enabled: false } as DebugFlags; | ||
| } |
There was a problem hiding this comment.
urlFlags is memoized with an empty dependency array, so changes to window.location.search after mount (client-side navigation, manual URL edits, history.pushState) will not update enabled/flags. If the intent is truly URL-driven debug mode, consider listening to popstate (and/or patching pushState/replaceState via an event) and re-parsing flags when the URL changes.
| const urlFlags = useMemo(() => { | |
| try { | |
| return typeof window !== 'undefined' | |
| ? parseDebugFlags(window.location.search) | |
| : { enabled: false } as DebugFlags; | |
| } catch { | |
| return { enabled: false } as DebugFlags; | |
| } | |
| const [urlFlags, setUrlFlags] = useState<DebugFlags>(() => { | |
| try { | |
| return typeof window !== 'undefined' | |
| ? parseDebugFlags(window.location.search) | |
| : ({ enabled: false } as DebugFlags); | |
| } catch { | |
| return { enabled: false } as DebugFlags; | |
| } | |
| }); | |
| useEffect(() => { | |
| if (typeof window === 'undefined') return; | |
| const handleLocationChange = () => { | |
| try { | |
| setUrlFlags(parseDebugFlags(window.location.search)); | |
| } catch { | |
| setUrlFlags({ enabled: false } as DebugFlags); | |
| } | |
| }; | |
| window.addEventListener('popstate', handleLocationChange); | |
| return () => { | |
| window.removeEventListener('popstate', handleLocationChange); | |
| }; |
| // Report render perf to DebugCollector when debug mode is active | ||
| if (isDebug && renderStart) { | ||
| const durationMs = performance.now() - renderStart; | ||
| DebugCollector.getInstance().addPerf({ | ||
| type: evaluatedSchema.type, | ||
| id: evaluatedSchema.id, | ||
| durationMs, | ||
| timestamp: Date.now(), | ||
| }); | ||
| } |
There was a problem hiding this comment.
DebugCollector.getInstance().addPerf(...) is executed during the SchemaRenderer render path and synchronously notifies subscribers. If DebugPanel is subscribed, this can trigger setState while another component is rendering (React warning: “Cannot update a component while rendering a different component”) and can also record aborted/strict-mode double renders. Consider deferring collection to an effect/commit phase (or making subscriber notifications async) to keep renders side-effect free.
Adds a universal
?__debugURL parameter to activate a floating developer panel across all@object-ui/*packages, with fine-grained sub-flags and SSR safety.@object-ui/core— debug flag parsing & data collectionDebugFlags,DebugCategory,parseDebugFlags(search?), enhancedisDebugEnabled()globalThis.OBJECTUI_DEBUG→process.env.OBJECTUI_DEBUG?__debug_schema,?__debug_perf,?__debug_data,?__debug_expr,?__debug_events,?__debug_registryDebugCollectorsingleton for centralized perf/expression/event data collection (tree-shakeable, capped at 200 entries)@object-ui/react— hook + context + renderer enhancementsuseDebugMode()→{ enabled, flags, toggle, setEnabled }withCtrl+Shift+DshortcutSchemaRendererContextextended with optionaldebugFlagspropagationSchemaRendererinjectsdata-debug-type/data-debug-idattributes when debug mode is activeSchemaRendererreports render performance toDebugCollectorfor the Perf tab@object-ui/components— DebugPanelsrc/debug/with 7 built-in tabs:extraTabspropapps/console— MetadataInspector backward compatibilityuseMetadataInspector()auto-opens when?__debugURL parameter is detectedTests
48 new tests across 5 test files: core debug flags (16), DebugCollector (10), useDebugMode hook (6), DebugPanel (13), SchemaRenderer debug attributes (3).
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.