Blog refresh: NestJS + Prisma series parts 2-5 (Prisma 7 / NestJS 11)#8034
Conversation
All code verified end-to-end on prisma@7.8 + @nestjs/core@11 against a real Prisma Postgres database (validation 400s, whitelist stripping, ParseIntPipe, P2002 409 filter, users CRUD, password exclusion, author include, full JWT login flow, bcrypt hashing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, web-repo blog skill conventions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll 4 parts) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughFour Prisma/NestJS blog tutorials (authentication, error handling, relational data, validation) were updated for Prisma ORM 7 and NestJS 11. Changes include refreshed front matter, narrative rewrites, JWT auth with bcrypt hashing, a Prisma-based exception filter, User/Article relations with password exclusion, and ValidationPipe/whitelist/ParseIntPipe documentation. ChangesAuthentication Tutorial
Error Handling Tutorial
Relational Data Tutorial
Validation Tutorial
Estimated code review effort: 2 (Simple) | ~15 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
apps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdx (1)
284-284: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider tightening "many different kinds of errors".
"many different kinds of" is wordy. A simpler phrasing like "various errors" reads more cleanly.
✏️ Suggested rewording
-Prisma throws the `PrismaClientKnownRequestError` for many different kinds of errors. +Prisma throws the `PrismaClientKnownRequestError` for various errors.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdx` at line 284, Tighten the phrasing in the PrismaClientKnownRequestError explanation by replacing the wordy “many different kinds of errors” with a shorter alternative like “various errors” in the surrounding markdown text. Update the sentence in the blog content so it reads more cleanly while keeping the rest of the PrismaClientKnownRequestError and code property explanation unchanged.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdx`:
- Around line 255-323: The P2002 branch in PrismaClientExceptionFilter is
exposing raw Prisma error details to clients. Update the catch method so it
still logs exception.message server-side, but returns a generic conflict
response for the P2002 case instead of using the raw exception.message in the
JSON payload. Keep the existing switch on exception.code in
PrismaClientExceptionFilter and adjust the response.status(...).json(...)
payload to use a user-friendly message while preserving the default super.catch
fallback for other Prisma errors.
In `@apps/blog/content/blog/nestjs-prisma-relational-data-7D056s1kOabc/index.mdx`:
- Line 790: Update the wording in the affected prose to hyphenate the compound
adjective “Prisma-generated types” instead of “Prisma generated types.” Locate
the sentence discussing ArticlesController, ArticleEntity, and
ClassSerializerInterceptor, and make the small text-only grammar correction
without changing any technical meaning.
In `@apps/blog/content/blog/nestjs-prisma-validation-7D056s1kOla1/index.mdx`:
- Line 39: The Node.js prerequisite in the tutorial is outdated and should be
raised to match the stack. Update the requirement text in the blog content to
specify Node.js v20 or higher instead of v18 or higher, and keep the wording
consistent with the setup instructions in this article.
---
Nitpick comments:
In `@apps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdx`:
- Line 284: Tighten the phrasing in the PrismaClientKnownRequestError
explanation by replacing the wordy “many different kinds of errors” with a
shorter alternative like “various errors” in the surrounding markdown text.
Update the sentence in the blog content so it reads more cleanly while keeping
the rest of the PrismaClientKnownRequestError and code property explanation
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fe67be97-db82-4f30-843c-8498c1051a9e
📒 Files selected for processing (4)
apps/blog/content/blog/nestjs-prisma-authentication-7D056s1s0k3l/index.mdxapps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdxapps/blog/content/blog/nestjs-prisma-relational-data-7D056s1kOabc/index.mdxapps/blog/content/blog/nestjs-prisma-validation-7D056s1kOla1/index.mdx
|
|
||
| > **Note**: There is a second file created called `src/prisma-client-exception/prisma-client-exception.filter.spec.ts` for creating tests. You can ignore this file for now. | ||
|
|
||
| You will get an error from `eslint` since the `catch` method is empty. Update the `catch` method implementation in `PrismaClientExceptionFilter` as follows: | ||
|
|
||
| ```typescript | ||
| // src/prisma-client-exception.filter.ts | ||
| ```ts | ||
| // src/prisma-client-exception/prisma-client-exception.filter.ts | ||
|
|
||
| +import { ArgumentsHost, Catch } from '@nestjs/common'; | ||
| +import { BaseExceptionFilter } from '@nestjs/core'; | ||
| +import { Prisma } from '@prisma/client'; | ||
| import { ArgumentsHost, Catch } from '@nestjs/common'; | ||
| import { BaseExceptionFilter } from '@nestjs/core'; | ||
| import { Prisma } from '../../generated/prisma/client'; | ||
|
|
||
| +@Catch(Prisma.PrismaClientKnownRequestError) // 1 | ||
| +export class PrismaClientExceptionFilter extends BaseExceptionFilter { // 2 | ||
| + catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | ||
| + console.error(exception.message); // 3 | ||
| @Catch(Prisma.PrismaClientKnownRequestError) // 1 | ||
| export class PrismaClientExceptionFilter extends BaseExceptionFilter { // 2 | ||
| catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | ||
| console.error(exception.message); // 3 | ||
|
|
||
| // default 500 error code | ||
| + super.catch(exception, host); | ||
| super.catch(exception, host); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Here you have made the following changes: | ||
|
|
||
| 1. To ensure that this filter catches exceptions of type `PrismaClientKnownRequestError`, you added it to the `@Catch` decorator. | ||
| 1. To ensure that this filter catches exceptions of type `PrismaClientKnownRequestError`, you added it to the `@Catch` decorator. Note the import: in Prisma 7, the `Prisma` namespace comes from your generated Client (`../../generated/prisma/client`), the same import path you used for the `Article` type in part one. Importing it from `@prisma/client` no longer works. | ||
| 2. The exception filter extends the `BaseExceptionFilter` class from the NestJS core package. This class provides a default implementation for the `catch` method that returns an "Internal server error" response to the user. You can learn more about this [in the NestJS docs](https://docs.nestjs.com/exception-filters#inheritance). | ||
| 3. You added a `console.error` statement to log the error message to the console. This is useful for debugging purposes. | ||
|
|
||
| Prisma throws the `PrismaClientKnownRequestError` for many different kinds of errors. So you will need to figure out how to extract the error code from the `PrismaClientKnownRequestError` exception. The `PrismaClientKnownRequestError` exception has a `code` property that contains the error code. You can find the list of error codes in the [Prisma Error Message reference](https://www.prisma.io/docs/orm/reference/error-reference#prisma-client-query-engine). | ||
| Prisma throws the `PrismaClientKnownRequestError` for many different kinds of errors. So you will need to figure out how to extract the error code from the `PrismaClientKnownRequestError` exception. The `PrismaClientKnownRequestError` exception has a `code` property that contains the error code. You can find the list of error codes in the [Prisma error message reference](https://www.prisma.io/docs/orm/reference/error-reference). | ||
|
|
||
| The error code you are looking for is `P2002`, which occurs for unique constraint violations. You will now update the `catch` method to throw an HTTP `409 Conflict` response in case of this error. You will also provide a custom error message to the user. | ||
|
|
||
|
|
||
| Update your exception filter implementation like this: | ||
|
|
||
| ```typescript | ||
| //src/prisma-client-exception.filter.ts | ||
| ```ts | ||
| // src/prisma-client-exception/prisma-client-exception.filter.ts | ||
|
|
||
| +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; | ||
| import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; | ||
| import { BaseExceptionFilter } from '@nestjs/core'; | ||
| import { Prisma } from '@prisma/client'; | ||
| +import { Response } from 'express'; | ||
| import { Prisma } from '../../generated/prisma/client'; | ||
| import { Response } from 'express'; | ||
|
|
||
| @Catch(Prisma.PrismaClientKnownRequestError) | ||
| export class PrismaClientExceptionFilter extends BaseExceptionFilter { | ||
| catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | ||
| console.error(exception.message); | ||
| + const ctx = host.switchToHttp(); | ||
| + const response = ctx.getResponse<Response>(); | ||
| + const message = exception.message.replace(/\n/g, ''); | ||
|
|
||
| + switch (exception.code) { | ||
| + case 'P2002': { | ||
| + const status = HttpStatus.CONFLICT; | ||
| + response.status(status).json({ | ||
| + statusCode: status, | ||
| + message: message, | ||
| + }); | ||
| + break; | ||
| + } | ||
| + default: | ||
| const ctx = host.switchToHttp(); | ||
| const response = ctx.getResponse<Response>(); | ||
| const message = exception.message.replace(/\n/g, ''); | ||
|
|
||
| switch (exception.code) { | ||
| case 'P2002': { | ||
| const status = HttpStatus.CONFLICT; | ||
| response.status(status).json({ | ||
| statusCode: status, | ||
| message: message, | ||
| }); | ||
| break; | ||
| } | ||
| default: | ||
| // default 500 error code | ||
| super.catch(exception, host); | ||
| + break; | ||
| + } | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
The P2002 response leaks internal file paths and query text to the client.
The message variable at line 304 is the raw exception.message, which includes internal paths (~/median/src/articles/articles.service.ts:11:32) and Prisma query internals. The example response at line 383 confirms this — the entire Prisma error trace is sent to the API consumer. While line 326 includes a general cautionary note, the tutorial's own example demonstrates the leak, and readers may copy it verbatim into production code.
Consider using a generic message for the P2002 case (e.g., "A record with this title already exists") and logging the detailed message server-side only. This also makes the example response cleaner and more instructive.
🛡️ Suggested improvement
case 'P2002': {
const status = HttpStatus.CONFLICT;
response.status(status).json({
statusCode: status,
- message: message,
+ message: 'A record with this value already exists.',
});
break;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| > **Note**: There is a second file created called `src/prisma-client-exception/prisma-client-exception.filter.spec.ts` for creating tests. You can ignore this file for now. | |
| You will get an error from `eslint` since the `catch` method is empty. Update the `catch` method implementation in `PrismaClientExceptionFilter` as follows: | |
| ```typescript | |
| // src/prisma-client-exception.filter.ts | |
| ```ts | |
| // src/prisma-client-exception/prisma-client-exception.filter.ts | |
| +import { ArgumentsHost, Catch } from '@nestjs/common'; | |
| +import { BaseExceptionFilter } from '@nestjs/core'; | |
| +import { Prisma } from '@prisma/client'; | |
| import { ArgumentsHost, Catch } from '@nestjs/common'; | |
| import { BaseExceptionFilter } from '@nestjs/core'; | |
| import { Prisma } from '../../generated/prisma/client'; | |
| +@Catch(Prisma.PrismaClientKnownRequestError) // 1 | |
| +export class PrismaClientExceptionFilter extends BaseExceptionFilter { // 2 | |
| + catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | |
| + console.error(exception.message); // 3 | |
| @Catch(Prisma.PrismaClientKnownRequestError) // 1 | |
| export class PrismaClientExceptionFilter extends BaseExceptionFilter { // 2 | |
| catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | |
| console.error(exception.message); // 3 | |
| // default 500 error code | |
| + super.catch(exception, host); | |
| super.catch(exception, host); | |
| } | |
| } | |
| ``` | |
| Here you have made the following changes: | |
| 1. To ensure that this filter catches exceptions of type `PrismaClientKnownRequestError`, you added it to the `@Catch` decorator. | |
| 1. To ensure that this filter catches exceptions of type `PrismaClientKnownRequestError`, you added it to the `@Catch` decorator. Note the import: in Prisma 7, the `Prisma` namespace comes from your generated Client (`../../generated/prisma/client`), the same import path you used for the `Article` type in part one. Importing it from `@prisma/client` no longer works. | |
| 2. The exception filter extends the `BaseExceptionFilter` class from the NestJS core package. This class provides a default implementation for the `catch` method that returns an "Internal server error" response to the user. You can learn more about this [in the NestJS docs](https://docs.nestjs.com/exception-filters#inheritance). | |
| 3. You added a `console.error` statement to log the error message to the console. This is useful for debugging purposes. | |
| Prisma throws the `PrismaClientKnownRequestError` for many different kinds of errors. So you will need to figure out how to extract the error code from the `PrismaClientKnownRequestError` exception. The `PrismaClientKnownRequestError` exception has a `code` property that contains the error code. You can find the list of error codes in the [Prisma Error Message reference](https://www.prisma.io/docs/orm/reference/error-reference#prisma-client-query-engine). | |
| Prisma throws the `PrismaClientKnownRequestError` for many different kinds of errors. So you will need to figure out how to extract the error code from the `PrismaClientKnownRequestError` exception. The `PrismaClientKnownRequestError` exception has a `code` property that contains the error code. You can find the list of error codes in the [Prisma error message reference](https://www.prisma.io/docs/orm/reference/error-reference). | |
| The error code you are looking for is `P2002`, which occurs for unique constraint violations. You will now update the `catch` method to throw an HTTP `409 Conflict` response in case of this error. You will also provide a custom error message to the user. | |
| Update your exception filter implementation like this: | |
| ```typescript | |
| //src/prisma-client-exception.filter.ts | |
| ```ts | |
| // src/prisma-client-exception/prisma-client-exception.filter.ts | |
| +import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; | |
| import { ArgumentsHost, Catch, HttpStatus } from '@nestjs/common'; | |
| import { BaseExceptionFilter } from '@nestjs/core'; | |
| import { Prisma } from '@prisma/client'; | |
| +import { Response } from 'express'; | |
| import { Prisma } from '../../generated/prisma/client'; | |
| import { Response } from 'express'; | |
| @Catch(Prisma.PrismaClientKnownRequestError) | |
| export class PrismaClientExceptionFilter extends BaseExceptionFilter { | |
| catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | |
| console.error(exception.message); | |
| + const ctx = host.switchToHttp(); | |
| + const response = ctx.getResponse<Response>(); | |
| + const message = exception.message.replace(/\n/g, ''); | |
| + switch (exception.code) { | |
| + case 'P2002': { | |
| + const status = HttpStatus.CONFLICT; | |
| + response.status(status).json({ | |
| + statusCode: status, | |
| + message: message, | |
| + }); | |
| + break; | |
| + } | |
| + default: | |
| const ctx = host.switchToHttp(); | |
| const response = ctx.getResponse<Response>(); | |
| const message = exception.message.replace(/\n/g, ''); | |
| switch (exception.code) { | |
| case 'P2002': { | |
| const status = HttpStatus.CONFLICT; | |
| response.status(status).json({ | |
| statusCode: status, | |
| message: message, | |
| }); | |
| break; | |
| } | |
| default: | |
| // default 500 error code | |
| super.catch(exception, host); | |
| + break; | |
| + } | |
| break; | |
| } | |
| } | |
| } | |
| ``` | |
| case 'P2002': { | |
| const status = HttpStatus.CONFLICT; | |
| response.status(status).json({ | |
| statusCode: status, | |
| message: 'A record with this value already exists.', | |
| }); | |
| break; | |
| } |
🧰 Tools
🪛 LanguageTool
[style] ~284-~284: ‘many different kinds of’ might be wordy. Consider a shorter alternative.
Context: ...the PrismaClientKnownRequestError for many different kinds of errors. So you will need to figure out ...
(EN_WORDINESS_PREMIUM_MANY_DIFFERENT_KINDS_OF)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blog/content/blog/nestjs-prisma-error-handling-7D056s1kOop2/index.mdx`
around lines 255 - 323, The P2002 branch in PrismaClientExceptionFilter is
exposing raw Prisma error details to clients. Update the catch method so it
still logs exception.message server-side, but returns a generic conflict
response for the P2002 case instead of using the raw exception.message in the
JSON payload. Keep the existing switch on exception.code in
PrismaClientExceptionFilter and adjust the response.status(...).json(...)
payload to use a user-friendly message while preserving the default super.catch
fallback for other Prisma errors.
|
|
||
|  | ||
|
|
||
| The reason for this issue is very similar to last time. Currently, the `ArticlesController` returns instances of Prisma generated types, whereas the `ClassSerializerInterceptor` works with the `UserEntity` class. To fix this, you will update the implementation of the `ArticleEntity` class and make sure it initializes the `author` property with an instance of `UserEntity`. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a hyphen to "Prisma-generated types".
"Prisma generated types" is a compound adjective modifying "types" and should be hyphenated.
✏️ Proposed grammar fix
-Currently, the `ArticlesController` returns instances of Prisma generated types, whereas the `ClassSerializerInterceptor` works with the `UserEntity` class.
+Currently, the `ArticlesController` returns instances of Prisma-generated types, whereas the `ClassSerializerInterceptor` works with the `UserEntity` class.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| The reason for this issue is very similar to last time. Currently, the `ArticlesController` returns instances of Prisma generated types, whereas the `ClassSerializerInterceptor` works with the `UserEntity` class. To fix this, you will update the implementation of the `ArticleEntity` class and make sure it initializes the `author` property with an instance of `UserEntity`. | |
| The reason for this issue is very similar to last time. Currently, the `ArticlesController` returns instances of Prisma-generated types, whereas the `ClassSerializerInterceptor` works with the `UserEntity` class. To fix this, you will update the implementation of the `ArticleEntity` class and make sure it initializes the `author` property with an instance of `UserEntity`. |
🧰 Tools
🪛 LanguageTool
[grammar] ~790-~790: Use a hyphen to join words.
Context: ...sControllerreturns instances of Prisma generated types, whereas theClassSeria...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blog/content/blog/nestjs-prisma-relational-data-7D056s1kOabc/index.mdx`
at line 790, Update the wording in the affected prose to hyphenate the compound
adjective “Prisma-generated types” instead of “Prisma generated types.” Locate
the sentence discussing ArticlesController, ArticleEntity, and
ClassSerializerInterceptor, and make the small text-only grammar correction
without changing any technical meaning.
Source: Linters/SAST tools
| To follow along with this tutorial, you will be expected to have: | ||
| To follow along with this tutorial, you will be expected to: | ||
|
|
||
| - ... have [Node.js](https://nodejs.org) (v18 or higher) installed. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the minimum Node.js version required for NestJS 11?
💡 Result:
The minimum Node.js version required for NestJS 11 is Node.js v20 [1][2][3][4]. Starting with version 11, support for Node.js v16 and v18 was dropped, making v20 the mandatory floor for the framework [1][2][3]. Official documentation recommends using the latest LTS (Long Term Support) version of Node.js to ensure the best experience and performance [1][2].
Citations:
- 1: https://docs.nestjs.com/migration-guide
- 2: https://github.com/nestjs/docs.nestjs.com/blob/ba85c6a2/content/migration.md
- 3: https://github.com/nestjs/nest/releases/tag/v11.0.0
- 4: https://docs.nestjs.com/first-steps
Update the Node.js requirement to v20+
NestJS 11 drops Node 18 support, so “v18 or higher” is misleading here. This tutorial should require Node.js 20 or newer to match the stack.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/blog/content/blog/nestjs-prisma-validation-7D056s1kOla1/index.mdx` at
line 39, The Node.js prerequisite in the tutorial is outdated and should be
raised to match the stack. Update the requirement text in the blog content to
specify Node.js v20 or higher instead of v18 or higher, and keep the wording
consistent with the setup instructions in this article.
What
Refreshes the remaining four posts of the NestJS + Prisma series (
nestjs-prisma-rest-api) for Prisma ORM 7.8 and NestJS 11, completing the series update started with part 1 in #7977:nestjs-prisma-validation-7D056s1kOla1)nestjs-prisma-error-handling-7D056s1kOop2)nestjs-prisma-relational-data-7D056s1kOabc)nestjs-prisma-authentication-7D056s1s0k3l)Slugs, Tasin Ishmam's byline, and original publish dates are preserved;
updatedAt: 2026-07-08added.Verification
The tutorial project was rebuilt from refreshed part 1 and every step of parts 2-5 was executed against
prisma@7.8/@nestjs/core@11with a live Prisma Postgres database. All error/response JSON in the posts is pasted from real responses: validation 400s,whiteliststripping (and the pre-whitelist date-injection exploit),ParseIntPiperejection, unhandled P2002 500 → filter 409, users CRUD with password exclusion viaClassSerializerInterceptor,include: { author }serialization, and the full JWT login flow with bcrypt hashing.Notable changes beyond version bumps
Prismanamespace and model types import from the generated client (generated/prisma/client), not@prisma/client; an explicitnpx prisma generatestep aftermigrate dev(on 7.8 migrate dev does not regenerate the client; verified twice).UserEntity.nameis nowstring | null,ArticleEntity.authorisUserEntity | null, null-handling onfindOne).<Accordions>(bodies server-rendered), first product mentions linked to docs, Prisma Next pull-quote replaced with a short plain closing note (applied to all four parts).src/prisma-client-exception/…),docker composesyntax,tsxseed runner, current package set (bcrypt@6,@nestjs/passport@11).Notes for review
migrate devregenerates the client and carries the old TOC/pull-quote; it needs the same small sweep.🤖 Generated with Claude Code
Summary by CodeRabbit