Skip to content

feat: URL-driven debug/developer panel (à la amis devtools)#844

Merged
hotlong merged 5 commits intomainfrom
copilot/add-url-driven-debug-panel
Feb 24, 2026
Merged

feat: URL-driven debug/developer panel (à la amis devtools)#844
hotlong merged 5 commits intomainfrom
copilot/add-url-driven-debug-panel

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 24, 2026

Adds a universal ?__debug URL 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 collection

  • Export DebugFlags, DebugCategory, parseDebugFlags(search?), enhanced isDebugEnabled()
  • Resolution order: URL params → globalThis.OBJECTUI_DEBUGprocess.env.OBJECTUI_DEBUG
  • Sub-flags: ?__debug_schema, ?__debug_perf, ?__debug_data, ?__debug_expr, ?__debug_events, ?__debug_registry
  • New DebugCollector singleton for centralized perf/expression/event data collection (tree-shakeable, capped at 200 entries)

@object-ui/react — hook + context + renderer enhancements

  • useDebugMode(){ enabled, flags, toggle, setEnabled } with Ctrl+Shift+D shortcut
  • SchemaRendererContext extended with optional debugFlags propagation
  • SchemaRenderer injects data-debug-type / data-debug-id attributes when debug mode is active
  • SchemaRenderer reports render performance to DebugCollector for the Perf tab

@object-ui/components — DebugPanel

  • Floating panel in src/debug/ with 7 built-in tabs:
    • Schema — current rendered JSON schema
    • Data — active data context
    • Perf — component render timing (highlights slow renders >16ms)
    • Expr — expression evaluation trace
    • Events — action/event timeline
    • Registry — all registered component types
    • Flags — current debug flag state
  • Plugin-extensible via extraTabs prop

apps/console — MetadataInspector backward compatibility

  • useMetadataInspector() auto-opens when ?__debug URL parameter is detected
import { useDebugMode } from '@object-ui/react';
import { DebugPanel } from '@object-ui/components';

function App() {
  const debug = useDebugMode(); // auto-detects ?__debug
  return (
    <>
      <MyApp />
      <DebugPanel
        open={debug.enabled}
        onClose={debug.toggle}
        flags={debug.flags}
        schema={currentSchema}
        dataContext={data}
      />
    </>
  );
}

Tests

48 new tests across 5 test files: core debug flags (16), DebugCollector (10), useDebugMode hook (6), DebugPanel (13), SchemaRenderer debug attributes (3).

Original prompt

This section details on the original issue you should resolve

<issue_title>通过URL参数驱动的通用调试/开发者面板(类amis devtools模式)</issue_title>
<issue_description>## 背景与��标

目前 ObjectUI 仅支持通过 globalThis.OBJECTUI_DEBUG 或环境变量切换调试输出,调试面板仅在 Console/少部分模块局部实现,难以统一扩展和全局可视化。类 amis 的 ?debug/devtools URL 参数驱动调试模式,将极大提升开发体验和问题诊断能力。

目标:

  • 通过 URL 参数(如 ?__debug)一键开启/关闭全局调试模式,显示"开发者调试面板"
  • 支持统一式的 Schema 查看、Data Context 查看、性能/表达式/事件追踪、组件注册表浏览等
  • 基础能力适配所有 @object-ui/* 层级,面向后续低代码生态和 VSCode 插件扩展

详细需求拆解

1. URL-Driven Debug Mode

  • 设计通用 debug 参数规范:如 ?__debug(主开关),?__debug_schema?__debug_perf?__debug_data 精细开关
  • @object-ui/core 增强 parseDebugFlags/isDebugEnabled(url/env/globalThis)
  • SSR/静态部署安全兼容(不会在 window 不存在时报错)
  • 支持全局和局部热切换(如快捷键、开关按钮)

2. Debug 状态与调试面板

  • @object-ui/react 实现 useDebugMode Hook,自动响应 URL/快捷键/手动切换,向 SchemaRendererContext 透传
  • 新增 DebugPanel 浮动调试面板(建议放 @object-ui/components/debug 目录,可插件扩展Tab)
  • Tab 初期实现:
    • Schema(当前渲染 JSON schema)
    • Data(schema data context)
    • Perf(组件渲染耗时与高亮慢组件)
    • Expr(表达式求值追踪)
    • Events(所有 Action/Event时间线)
    • Registry(所有注册类型/组件)

3. SchemaRenderer/渲染运行时增强

  • 支持注入 data-debug-* 属性到组件,hover/聚焦 DOM 高亮
  • 跟踪子组件渲染次数、性能数据,并收集到上报给 DebugPanel

4. 向后兼容 Console MetadataInspector

  • apps/console: MetadataInspector 保持向后兼容,debug模式自动增强为全域调试面板

5. 代码解耦与扩展性

  • Debug 数据采集应不影响生产包体积与性能,支持 TreeShaking
  • 调试数据 UI/采集代码彻底解耦,面向插件扩展、VSCode插件等未来场景

长远扩展考量

  • 支持后续接入更多自定义Tab,如API流控、Schema溯源分析、权限等
  • 打通 VSCode 插件、本地调试代理、自动诊断能力

Best Practice: 关键实现细节建议参考 amis/devtools、AntDesign Pro Devtools、React DevTools。调试代码请隔离核心渲染主流程(副作用/TreeShaking友好),并具备良好单元测试。完成后请运行 test 并补充 ROADMAP。</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectui Ready Ready Preview, Comment Feb 24, 2026 5:48pm
objectui-demo Ready Ready Preview, Comment Feb 24, 2026 5:48pm
objectui-storybook Ready Ready Preview, Comment Feb 24, 2026 5:48pm

Request Review

…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 AI changed the title [WIP] Add URL driven debug panel for ObjectUI feat: URL-driven debug/developer panel (à la amis devtools) Feb 24, 2026
Copilot AI requested a review from hotlong February 24, 2026 17:26
@hotlong
Copy link
Copy Markdown
Collaborator

hotlong commented Feb 24, 2026

@copilot 仔细评估是否完成任务要求的所有功能

Copilot AI and others added 2 commits February 24, 2026 17:38
…, 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>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 24, 2026

@copilot 仔细评估是否完成任务要求的所有功能

Completed all missing features in commit c61b660 and 0e84e99. Here's the full checklist against the issue requirements:

1. URL-Driven Debug Mode

  • ?__debug master switch + ?__debug_schema/perf/data/expr/events/registry sub-flags
  • parseDebugFlags() / isDebugEnabled() (URL → globalThis → env)
  • SSR-safe (all paths wrapped in try/catch, typeof window guards)
  • Ctrl+Shift+D keyboard shortcut + manual toggle button

2. Debug Panel Tabs (7/7)

  • Schema, Data, Perf (highlights >16ms renders), Expr (expression trace), Events (action timeline), Registry, Flags

3. SchemaRenderer Enhancement

  • data-debug-type / data-debug-id attributes injected when debug enabled
  • Render perf reported to DebugCollector → feeds the Perf tab

4. Console MetadataInspector

  • useMetadataInspector() auto-opens when ?__debug URL param detected

5. Code Decoupling

  • DebugCollector in core (tree-shakeable singleton, no UI deps)
  • DebugPanel in components/debug/ (only imported when used)
  • Plugin-extensible via extraTabs

Tests: 48 new tests across 5 files. All 512 react tests + 744 core tests pass.

@hotlong hotlong marked this pull request as ready for review February 24, 2026 17:50
Copilot AI review requested due to automatic review settings February 24, 2026 17:50
@hotlong hotlong merged commit 00c9160 into main Feb 24, 2026
4 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 DebugFlags parsing (parseDebugFlags) and DebugCollector singleton to @object-ui/core, plus enhanced isDebugEnabled() resolution order.
  • Added useDebugMode() hook and debugFlags propagation via SchemaRendererContext; SchemaRenderer now injects data-debug-* attributes and reports perf entries.
  • Added DebugPanel (plugin-extensible tabs) to @object-ui/components, and updated Console MetadataInspector to auto-open when ?__debug is 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.

Comment on lines 233 to 235
debugTime(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
const renderStart = isDebug ? performance.now() : 0;
const rendered = (
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
debugLog,
debugTime,
debugTimeEnd,
DebugCollector,
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
DebugCollector,

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +50
function SchemaTab({ schema }: { schema?: unknown }) {
if (!schema) {
return <p className="text-xs text-muted-foreground italic">No schema available</p>;
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
}

function DataTab({ dataContext }: { dataContext?: unknown }) {
if (!dataContext) {
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (!dataContext) {
if (dataContext == null) {

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +76
function RegistryTab() {
const entries = useMemo(() => {
try {
return ComponentRegistry.getAllTypes();
} catch {
return [];
}
}, []);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +55
const urlFlags = useMemo(() => {
try {
return typeof window !== 'undefined'
? parseDebugFlags(window.location.search)
: { enabled: false } as DebugFlags;
} catch {
return { enabled: false } as DebugFlags;
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);
};

Copilot uses AI. Check for mistakes.
Comment on lines +253 to +262
// 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(),
});
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

通过URL参数驱动的通用调试/开发者面板(类amis devtools模式)

3 participants