From c1c6f36176ef9e017d110a000f2d412990ce8a5d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 22:00:58 +0000 Subject: [PATCH] fix(mcp): list reporting tabs for an unknown NgRx page inspect-signal-store and signal-store-history answered an unknown `page` with "No page X is reporting NgRx state" followed by the "No NgRx state has been reported yet" text, even when other tabs were reporting. That told agents nothing was connected and gave them no id to retry with, and it broke the promise in the agent tools docs that an unknown page lists the tabs that do report. Both tools now answer with unknownPageText, as dispatch-ngrx-action and the other page tools already do. With no tab reporting at all they still give the original no-data answer. --- .../src/rpc/__tests__/ngrx-live-tools.test.ts | 44 +++++++++++++++++++ packages/devtools/src/rpc/ngrx-live-tools.ts | 18 +++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/devtools/src/rpc/__tests__/ngrx-live-tools.test.ts b/packages/devtools/src/rpc/__tests__/ngrx-live-tools.test.ts index dc3d33cc..4e77ca9b 100644 --- a/packages/devtools/src/rpc/__tests__/ngrx-live-tools.test.ts +++ b/packages/devtools/src/rpc/__tests__/ngrx-live-tools.test.ts @@ -306,3 +306,47 @@ describe('withUntrustedPreamble', () => { expect(text.length).toBeLessThanOrEqual(NGRX_LIVE_TOOL_MAX + 200); }); }); + +describe('an unknown page', () => { + function twoPages(): NgrxPages { + const pages: NgrxPages = new Map(); + for (const [pid, at] of [ + ['p1', 1_000], + ['p2', 2_000], + ] as const) { + mergeNgrxReport( + pages, + { + pageId: pid, + session: 's', + url: `/${pid}`, + title: '', + stores: [store()], + classic: null, + log: [logEntry({ seq: 1 })], + }, + [], + at, + ); + } + return pages; + } + + const tools = [ + ['inspect-signal-store', (pages: NgrxPages) => inspectSignalStoreText(pages, 'p3')], + ['signal-store-history', (pages: NgrxPages) => signalStoreHistoryText(pages, 'p3')], + ] as const; + + for (const [name, answer] of tools) { + it(`${name} lists the pages that do report, newest first`, () => { + const text = answer(twoPages()); + expect(text).toContain('No page `p3` is reporting NgRx state.'); + expect(text).toMatch(/`p2`.*`p1`/); + expect(text).not.toMatch(/no ngrx state has been reported/i); + }); + + it(`${name} still says nothing was reported when no page reports`, () => { + expect(answer(new Map())).toMatch(/no ngrx state has been reported/i); + }); + } +}); diff --git a/packages/devtools/src/rpc/ngrx-live-tools.ts b/packages/devtools/src/rpc/ngrx-live-tools.ts index 709e5290..32aca033 100644 --- a/packages/devtools/src/rpc/ngrx-live-tools.ts +++ b/packages/devtools/src/rpc/ngrx-live-tools.ts @@ -1,5 +1,6 @@ import type { NgrxPageRecord, NgrxPages } from './ngrx-tools.ts'; import type { NgrxLogEntry, NgrxSignalStoreInfo } from '../ngrx-shared.ts'; +import { unknownPageText } from './pages.ts'; /** * `inspectSignalStoreText`/`signalStoreHistoryText` output is capped to this @@ -26,6 +27,15 @@ function pagesOf(pages: NgrxPages, pageId?: string): NgrxPageRecord[] { return pageId ? all.filter((p) => p.pageId === pageId) : all; } +/** + * The answer when no page matches. An unknown `page` lists the tabs that do report + * NgRx state, like the other page tools; with no tab reporting at all it is `NO_DATA`. + */ +function noMatchText(pages: NgrxPages, pageId?: string): string { + if (pageId && pages.size) return unknownPageText(pageId, [...pages.values()], 'NgRx state'); + return pageId ? `No page ${code(pageId)} is reporting NgRx state.\n\n${NO_DATA}` : NO_DATA; +} + function storeLabel(store: NgrxSignalStoreInfo): string { return store.name ?? store.className; } @@ -119,9 +129,7 @@ export function inspectSignalStoreText( storeId?: string, ): string { const matching = pagesOf(pages, pageId); - if (!matching.length) { - return pageId ? `No page ${code(pageId)} is reporting NgRx state.\n\n${NO_DATA}` : NO_DATA; - } + if (!matching.length) return noMatchText(pages, pageId); if (storeId) { for (const page of matching) { const store = page.stores.find((s) => s.id === storeId); @@ -181,9 +189,7 @@ export function signalStoreHistoryText( since?: number, ): string { const matching = pagesOf(pages, pageId); - if (!matching.length) { - return pageId ? `No page ${code(pageId)} is reporting NgRx state.\n\n${NO_DATA}` : NO_DATA; - } + if (!matching.length) return noMatchText(pages, pageId); if (typeof since === 'number' && matching.length > 1) { return `Pass \`page\` to identify a single page when using \`since\`; sequence numbers are per-page and cannot be compared across pages.`; }