Skip to content

Commit 03bb770

Browse files
committed
fix(http): name a hydration mismatch as a node unless its class is known
explain-ssr-request printed every mismatch as "Mismatch in `x`", so a node name such as `app-card` or `#text` read like a component class. The scan already sets isClass when ng.getComponent names the host. The agent text now says "Mismatch in component `ProductCard`" only for a class, and "Mismatch at element `<app-card>`" or "at a `#text` node" otherwise, as the panel and hub messages already do. Refs #366
1 parent 74eb853 commit 03bb770

4 files changed

Lines changed: 41 additions & 4 deletions

File tree

‎apps/docs/src/content/agents/tools.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ These tools read the HttpClient calls that `withPangular()` recorded and the SSR
855855
| `explain-ssr-request` | One request end to end: timings, kept response headers, the router's guard and resolver times, each server call with its transfer cache outcome, then the browser page with its hydration result and the calls the browser made again. | `id`, `url` |
856856
| `list-http-calls` | The HttpClient calls of one tab and the server render that served it, newest first: side (SSR or client), method, URL, status, duration, and flags for mocked, faulted, cancelled, redirected, transfer cache hit and the matched rule. With `preview`, the masked response body, for failed calls too. With `view: "waterfall"`, the tab's client calls as a waterfall instead. | `url`, `failed`, `limit`, `preview`, `headers`, `view` |
857857

858-
`explain-ssr-request` picks the request by `id`, else the newest one for `url`, else the newest one.
858+
`explain-ssr-request` picks the request by `id`, else the newest one for `url`, else the newest one. It names each hydration mismatch by the component class the node hosts when the page could read one, else by the element (`<app-card>`) or text node (`#text`).
859859

860860
`list-http-calls` lists one tab and the server calls of the SSR request that served it: the tab `page` names, or the default page. With no tab connected, it lists the server calls. `url` keeps calls whose URL contains the text. `failed` keeps calls with a status of 400 or more, no response, an error, or a cancel. `limit` defaults to 30 (at most 200). `preview` adds each call's response preview, including the body a failed call got back, such as a validation message or `problem+json`. `headers` adds each call's request and response headers. Secrets in URLs, errors, previews and headers are masked, and `url` matches the masked URL.
861861

‎packages/devtools/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ What remains: `/config` exports `DEFAULT_POPUP_HOTKEY`, `PANGULAR_ACTIONS`, `PAN
230230
- Browser DevTools show the app's own line as the caller of a `console` call again. The browser entries ship source maps that list the console tap in `x_google_ignoreList`, so its wrapper frame is skipped (#369).
231231
- The Angular Native overlay gates the injector profiler like the NativeScript overlay: in a development build with no `window`, importing it defines one until Angular publishes its debug utilities, so the **Providers** lists fill (#371).
232232
- `record-change-detection` with `record: "start"` passes on why a page that answered could not record, from its capability report: the Angular version it runs, or a profiler hook the recording could not attach to yet. It no longer waits out the 3 seconds and blames the Angular version (#366).
233+
- `explain-ssr-request` names a hydration mismatch as a component only when the page read its class, and as an element (`<app-card>`) or text node (`#text`) otherwise (#366).
233234

234235
### Docs
235236

‎packages/devtools/src/__tests__/ssr-requests.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,31 @@ describe('ssrRequestStory', () => {
361361
expect(text).toContain('POST `/api/quote`');
362362
});
363363

364+
it('names a mismatch by its component class only when the page read one', () => {
365+
const pages = [
366+
page({
367+
hydration: {
368+
enabled: true,
369+
nodes: { hydrated: 4, skipped: 0, mismatched: 3 },
370+
mismatches: [
371+
{ component: 'ProductCard', isClass: true },
372+
{ component: 'app-card' },
373+
{ component: '#text' },
374+
],
375+
skipHydrationHosts: [],
376+
warnings: [],
377+
warningsCaptured: true,
378+
},
379+
}),
380+
];
381+
const text = explainSsrRequestText([ssrRequest()], [], pages, {});
382+
expect(text).toContain('Mismatch in component `ProductCard`');
383+
expect(text).toContain('Mismatch at element `<app-card>`');
384+
expect(text).toContain('Mismatch at a `#text` node');
385+
expect(text).not.toContain('Mismatch in `app-card`');
386+
expect(text).not.toContain('Mismatch in `#text`');
387+
});
388+
364389
it('explains an unknown id and an empty list', () => {
365390
expect(explainSsrRequestText([], [], [], {})).toContain('devtools.ssrMiddleware');
366391
expect(explainSsrRequestText([ssrRequest()], [], [], { id: 'nope' })).toContain(

‎packages/devtools/src/rpc/ssr-tools.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,19 @@ export function ssrRequestStory(
193193
return { request, serverCalls: own, page, refetched };
194194
}
195195

196+
/**
197+
* One hydration mismatch for the agent text. The page names it by the
198+
* component class its node hosts when it could read one (`isClass`); otherwise
199+
* `component` is the node name (`app-card`, `#text`), so it is shown as a node,
200+
* not as a component.
201+
*/
202+
export function mismatchLine(m: { component: string; isClass?: boolean }): string {
203+
if (m.isClass) return `Mismatch in component ${code(m.component)}`;
204+
return m.component.startsWith('#')
205+
? `Mismatch at a ${code(m.component)} node`
206+
: `Mismatch at element ${code(`<${m.component}>`)}`;
207+
}
208+
196209
function ms(value: number): string {
197210
return `${value} ms`;
198211
}
@@ -348,9 +361,7 @@ export function explainSsrRequestText(
348361
: `${h.nodes?.skipped ?? 0} nodes skipped`
349362
}.`,
350363
);
351-
for (const m of h.mismatches.slice(0, 10)) {
352-
lines.push(` - Mismatch in ${code(m.component)}`);
353-
}
364+
for (const m of h.mismatches.slice(0, 10)) lines.push(` - ${mismatchLine(m)}`);
354365
for (const w of h.warnings.slice(0, 5)) lines.push(` - ${code(w)}`);
355366
}
356367
if (h?.stableMs !== undefined) {

0 commit comments

Comments
 (0)