From 2ae7e7028e2ced2c82b6996b758f4a78d0eb49a6 Mon Sep 17 00:00:00 2001 From: Edu Date: Tue, 16 Jun 2026 21:33:01 +0000 Subject: [PATCH 1/2] feat(core): expose debuggableFn for effects in signal debug graph Expose the underlying user function (fn) as debuggableFn for effect nodes in the signal debug graph. This allows debugging tools to locate and set breakpoints on effects. --- packages/core/src/render3/util/signal_debug.ts | 8 ++++++++ packages/core/test/acceptance/signal_debug_spec.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/packages/core/src/render3/util/signal_debug.ts b/packages/core/src/render3/util/signal_debug.ts index d5519d4c4a7d..4aca04f57240 100644 --- a/packages/core/src/render3/util/signal_debug.ts +++ b/packages/core/src/render3/util/signal_debug.ts @@ -134,6 +134,14 @@ function getNodesAndEdgesFromSignalMap(signalMap: ReadonlyMap unknown) | undefined, id, }); + } else if (isEffectNode(consumer)) { + debugSignalGraphNodes.push({ + label: consumer.debugName, + kind: consumer.kind, + epoch: consumer.version, + debuggableFn: consumer.fn, + id, + }); } else { debugSignalGraphNodes.push({ label: consumer.debugName, diff --git a/packages/core/test/acceptance/signal_debug_spec.ts b/packages/core/test/acceptance/signal_debug_spec.ts index a92634ccb6e9..ba3f6c2d815d 100644 --- a/packages/core/test/acceptance/signal_debug_spec.ts +++ b/packages/core/test/acceptance/signal_debug_spec.ts @@ -122,6 +122,8 @@ describe('getSignalGraph', () => { const effectNode = nodes.find((node) => node.label === 'primitiveSignalEffect')!; expect(effectNode).toBeDefined(); + expect(effectNode.debuggableFn).toBeDefined(); + expect(typeof effectNode.debuggableFn).toBe('function'); const signalNode = nodes.find((node) => node.label === 'primitiveSignal')!; expect(signalNode).toBeDefined(); From 6a0fd1c03b8dbe0d4bf32a8ba390ce68c7fc9865 Mon Sep 17 00:00:00 2001 From: Edu Date: Tue, 16 Jun 2026 21:33:04 +0000 Subject: [PATCH 2/2] feat(devtools): add prototype for setting signal breakpoints Add a 'Set Breakpoint' button to the signal details panel. It uses the Chrome Console Utilities debug() and inspect() APIs to programmatically set breakpoints and navigate to the source code without requiring extra permissions for the prototype. --- .../src/lib/application-operations/index.ts | 1 + .../signal-graph-pane.component.html | 1 + .../signal-graph-pane.component.ts | 11 +++++++++++ .../signal-details/signal-details.component.html | 11 +++++++++++ .../signal-details/signal-details.component.ts | 1 + .../src/app/chrome-application-operations.ts | 15 +++++++++++++++ devtools/src/demo-application-operations.ts | 7 +++++++ 7 files changed, 47 insertions(+) diff --git a/devtools/projects/ng-devtools/src/lib/application-operations/index.ts b/devtools/projects/ng-devtools/src/lib/application-operations/index.ts index ae00f791819f..5ef08c460cd0 100644 --- a/devtools/projects/ng-devtools/src/lib/application-operations/index.ts +++ b/devtools/projects/ng-devtools/src/lib/application-operations/index.ts @@ -14,6 +14,7 @@ export abstract class ApplicationOperations { abstract selectDomElement(position: ElementPosition, target: Frame): void; abstract inspect(directivePosition: DirectivePosition, objectPath: string[], target: Frame): void; abstract inspectSignal(position: SignalNodePosition, target: Frame): void; + abstract setSignalBreakpoint(position: SignalNodePosition, target: Frame): void; abstract viewSourceFromRouter(name: string, type: string, target: Frame): void; abstract setStorageItems(items: {[key: string]: unknown}): Promise; abstract getStorageItems(items: string[]): Promise<{[key: string]: unknown}>; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.html index 8a58b84b2b1c..822eaa03853c 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.html @@ -15,6 +15,7 @@ [graph]="signalGraph.graph()!" [element]="signalGraph.element()!" (gotoSource)="gotoSource($event)" + (setBreakpoint)="setBreakpoint($event)" (expandCluster)="expandCluster($event)" (highlightDeps)="highlightDeps($event)" (close)="detailsVisible.set(false)" diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.ts index 87673a31e5cf..fb4ae568cdb6 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane/signal-graph-pane.component.ts @@ -104,6 +104,17 @@ export class SignalGraphPaneComponent { ); } + setBreakpoint(node: DevtoolsSignalGraphNode) { + const frame = this.frameManager.selectedFrame(); + this.appOperations.setSignalBreakpoint( + { + element: this.signalGraph.element()!, + signalId: node.id, + }, + frame!, + ); + } + expandCluster(clusterId: string) { this.visualizer().expandCluster(clusterId); } diff --git a/devtools/projects/ng-devtools/src/lib/shared/signal-details/signal-details.component.html b/devtools/projects/ng-devtools/src/lib/shared/signal-details/signal-details.component.html index fb6e55b44e7e..a30992872dee 100644 --- a/devtools/projects/ng-devtools/src/lib/shared/signal-details/signal-details.component.html +++ b/devtools/projects/ng-devtools/src/lib/shared/signal-details/signal-details.component.html @@ -16,6 +16,17 @@ > code + } @else if (isClusterNode) {