@polymorphism-tech/morph-spec 9.4.0 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +2 -2
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/MORPH.md +23 -33
  7. package/framework/agents.json +6 -7
  8. package/framework/docs/CLI.md +3 -3
  9. package/framework/hooks/README.md +2 -2
  10. package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
  11. package/framework/hooks/claude-code/core/comment-guard.js +1 -0
  12. package/framework/hooks/claude-code/core/denylist.json +63 -1
  13. package/framework/hooks/claude-code/core/enforce-phase-writes.js +0 -8
  14. package/framework/hooks/claude-code/core/gate-guard.js +0 -8
  15. package/framework/hooks/claude-code/core/loop-detect.js +0 -3
  16. package/framework/hooks/claude-code/core/pre-bash-denylist.js +2 -1
  17. package/framework/hooks/claude-code/core/protect-spec-files.js +0 -7
  18. package/framework/hooks/claude-code/core/validate-completion.js +0 -4
  19. package/framework/hooks/claude-code/core/worktree-context.js +0 -2
  20. package/framework/hooks/claude-code/statusline.py +3 -44
  21. package/framework/hooks/shared/build-check.js +12 -19
  22. package/framework/hooks/shared/comment-rules.js +11 -1
  23. package/framework/hooks/shared/hook-response.js +0 -4
  24. package/framework/hooks/shared/payload-utils.js +1 -35
  25. package/framework/hooks/shared/phase-utils.js +0 -12
  26. package/framework/hooks/shared/slice-progress.js +0 -2
  27. package/framework/hooks/shared/state-reader.js +0 -19
  28. package/framework/hooks/shared/telemetry-logger.js +1 -1
  29. package/framework/rules/frontend-standards.md +1 -2
  30. package/framework/rules/nextjs-standards.md +9 -8
  31. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  32. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  33. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  34. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  35. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  36. package/framework/skills/core/morph-media/SKILL.md +1 -1
  37. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  38. package/framework/skills/core/morph-review/SKILL.md +1 -1
  39. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  40. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  41. package/framework/standards/STANDARDS.json +32 -29
  42. package/framework/standards/STANDARDS.meta.json +13 -15
  43. package/framework/standards/architecture/code-comments.md +1 -1
  44. package/framework/standards/frontend/admin-app/data-table.md +1 -3
  45. package/framework/standards/frontend/admin-app/page-structure.md +8 -4
  46. package/framework/standards/frontend/nextjs/app-router.md +3 -4
  47. package/framework/standards/frontend/nextjs/components.md +2 -15
  48. package/framework/standards/frontend/nextjs/data-fetching.md +2 -3
  49. package/framework/standards/frontend/nextjs/lint.md +141 -0
  50. package/framework/standards/frontend/nextjs/motion-patterns.md +2 -7
  51. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -2
  52. package/framework/standards/frontend/nextjs/nextjs-patterns.md +5 -7
  53. package/framework/standards/frontend/nextjs/project-structure.md +3 -2
  54. package/framework/standards/frontend/nextjs/state-management.md +6 -7
  55. package/framework/templates/README.md +4 -4
  56. package/framework/templates/REGISTRY.json +21 -309
  57. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +54 -56
  58. package/package.json +3 -2
  59. package/src/core/paths/output-schema.js +0 -9
  60. package/src/core/state/feature-identity.js +0 -2
  61. package/src/core/state/state-manager.js +1 -169
  62. package/src/core/templates/template-renderer.js +0 -23
  63. package/src/dashboard/app/lib/format.js +0 -7
  64. package/src/dashboard/app/lib/phases.js +0 -7
  65. package/src/dashboard/scanner/activity-scanner.js +0 -4
  66. package/src/lib/ai/ai-project-scan.js +1 -6
  67. package/src/lib/graph/graphify-runtime.js +0 -1
  68. package/src/lib/mutation/snapshot.js +1 -5
  69. package/src/lib/risk-detector.js +0 -5
  70. package/src/lib/stack/stack-profile.js +0 -4
  71. package/src/lib/stack/stack-scan.js +15 -4
  72. package/src/lib/tasks/eval-node.js +1 -7
  73. package/src/lib/validators/validation-runner.js +0 -43
  74. package/src/lib/validators/verify-runner.js +18 -2
  75. package/src/utils/agents-installer.js +12 -19
  76. package/src/utils/claude-settings-manager.js +0 -28
  77. package/src/utils/file-copier.js +0 -53
  78. package/framework/hooks/shared/activity-logger.js +0 -131
  79. package/framework/standards/frontend/design-system/naming.md +0 -68
  80. package/src/commands/validation/index.js +0 -2
  81. package/src/lib/validators/code-budget/index.js +0 -2
  82. package/src/lib/validators/contracts/index.js +0 -1
  83. package/src/lib/validators/nextjs/index.js +0 -1
  84. package/src/lib/validators/packages/index.js +0 -1
  85. package/src/lib/validators/reuse/index.js +0 -2
@@ -1211,26 +1211,7 @@
1211
1211
  "tokens": "#stagger--timing",
1212
1212
  "principles": "#css-animation-patterns"
1213
1213
  },
1214
- "digest": "CSS animation library for design-system.css: keyframes (fadeIn/Out, slideIn Up/Down/Left/Right, scaleIn, bounce, pulse, spin, shimmer) exposed as `.animate-*` classes (0.3s fade/scale, 0.5s slides; bounce 1s, pulse 2s, spin 1s infinite) plus `.duration-fast/normal/slow` (150/300/500ms), `.stagger-1..10` (0.1s steps) and `.ease-bounce`. For staggered lists set `animation-delay` AND `animation-fill-mode: backwards` (marked CRITICAL: otherwise elements flash before the delay). Skeletons use `.shimmer` (1.5s infinite). Hover/focus transitions run 0.15s to 0.3s. Always include the `@media (prefers-reduced-motion: reduce)` block (0.01ms durations, one iteration). Beyond CSS: Motion (formerly Framer Motion) for simple enter/exit, layout and gesture transitions (`frontend/nextjs/motion-patterns.md`); GSAP for ScrollTrigger, SVG morph, timelines (`frontend/gsap/gsap-core.md`)."
1215
- },
1216
- {
1217
- "id": "frontend-design-system-naming",
1218
- "name": "naming",
1219
- "path": "frontend/design-system/naming.md",
1220
- "category": "frontend",
1221
- "subcategory": "design-system",
1222
- "tags": [
1223
- "frontend",
1224
- "design-system"
1225
- ],
1226
- "stacks": [
1227
- "*"
1228
- ],
1229
- "anchors": {
1230
- "blocks": "#bem-for-reusable-components",
1231
- "tokens": "#css-variables-with-namespace"
1232
- },
1233
- "digest": "CSS naming rests on four principles: a page prefix prevents conflicts, BEM for components gives clear hierarchy, namespaced variables avoid library collisions, and state suffixes keep modifiers consistent. Page-specific elements take the page name as prefix (.home-hero, .checkout-form, .dashboard-stats, .auth-form); reusable components use BEM: block .card, element .card__header, modifier .card--featured. Rule: BEM for reusable components, page prefix for page-specific elements. Custom properties go in :root under the --app- namespace (--app-primary, --app-spacing-md, --app-font-heading). State modifiers use fixed suffixes: --active, --disabled, --loading, --error (.button, .button--active, .button--disabled)."
1214
+ "digest": "CSS animation library for design-system.css (keyframes and the `.animate-*`/`.stagger-*` utility classes live in the file, not in a rule). For staggered lists set `animation-delay` AND `animation-fill-mode: backwards` (marked CRITICAL: otherwise elements flash before the delay). Always include the `@media (prefers-reduced-motion: reduce)` block (0.01ms durations, one iteration). Beyond CSS: Motion (formerly Framer Motion) for simple enter/exit, layout and gesture transitions (`frontend/nextjs/motion-patterns.md`); GSAP for ScrollTrigger, SVG morph, timelines (`frontend/gsap/gsap-core.md`)."
1234
1215
  },
1235
1216
  {
1236
1217
  "id": "frontend-design-system-premium-finish",
@@ -1341,7 +1322,7 @@
1341
1322
  "layout": "#root-layout--required-providers",
1342
1323
  "routing": "#app-directory-structure"
1343
1324
  },
1344
- "digest": "Next.js 15 App Router, TypeScript only (no .js/.jsx). Server Components by default; add `'use client'` only for interaction (onClick, useState/useEffect/useRef), browser APIs, TanStack Query hooks and interactive shadcn/ui. `app/` is for routing only: business logic goes to `features/`, never in `page.tsx`. Never fetch data in a Client Component when a Server Component can, and never fetch in `useEffect`: use a Server Component or TanStack Query. Co-locate `loading.tsx` and `error.tsx` (`error.tsx` must be a Client Component) beside `page.tsx`. Server Components call the .NET backend directly using the dedicated `BACKEND_API_PREFIX` from `@/lib/api/prefixes`, never the same-origin proxy prefix used by Client Components; always check `res.ok`. Avoid `route.ts` API routes (use the .NET API). The root `layout.tsx` wraps children in `QueryProvider` and the sonner `Toaster`."
1325
+ "digest": "Next.js 15 App Router, TypeScript only (no .js/.jsx). Server Components by default; add `'use client'` only for interaction (onClick, useState/useEffect/useRef), browser APIs, TanStack Query hooks and interactive shadcn/ui. `app/` is for routing only: business logic goes to `features/`, never in `page.tsx`. Never fetch data in a Client Component when a Server Component can,. Co-locate `loading.tsx` and `error.tsx` beside `page.tsx`. Server Components call the .NET backend directly using the dedicated `BACKEND_API_PREFIX` from `@/lib/api/prefixes`, never the same-origin proxy prefix used by Client Components; always check `res.ok`. Avoid `route.ts` API routes (use the .NET API). The root `layout.tsx` wraps children in `QueryProvider` and the sonner `Toaster`. Fetch in `useEffect` and `'use client'` in the root layout are lint errors (frontend-nextjs-lint)."
1345
1326
  },
1346
1327
  {
1347
1328
  "id": "frontend-nextjs-components",
@@ -1360,7 +1341,7 @@
1360
1341
  "patterns": "#three-tier-hierarchy",
1361
1342
  "shadcn": "#tier-1--shadcnui-primitives"
1362
1343
  },
1363
- "digest": "Three tiers (Next.js 16 + shadcn 4 CLI): `components/ui/` is shadcn CLI output (add via `npx shadcn@latest add`, NEVER edit because the CLI regenerates it; wrap it in `components/` instead); `components/` holds shared composed components with no business logic and NEVER imports from `features/`; `features/*/components/` is feature-scoped and may know the domain. Add `'use client'` only if the component uses hooks, events or browser APIs (not on every component: unnecessary client JS). Never pass raw API/fetch data as props: transform it into typed props and validate with Zod first. Props interfaces always accept an optional `className` for Tailwind override. The shared data table is specified once in `admin-app/data-table.md`: do not write a second `components/data-table.tsx`; a feature list is a Tier 3 component that owns the columns and the URL-to-table mapping."
1344
+ "digest": "Three tiers (Next.js 16 + shadcn 4 CLI): `components/ui/` is shadcn CLI output (add via `npx shadcn@latest add`, NEVER edit because the CLI regenerates it; wrap it in `components/` instead); `components/` holds shared composed components with no business logic (importing `features/` is a lint error, frontend-nextjs-lint); `features/*/components/` is feature-scoped and may know the domain. Add `'use client'` only if the component uses hooks, events or browser APIs (not on every component: unnecessary client JS). Never pass raw API/fetch data as props: the feature maps it to typed props. The shared data table is specified once in `admin-app/data-table.md`: do not write a second `components/data-table.tsx`; a feature list is a Tier 3 component that owns the columns and the URL-to-table mapping."
1364
1345
  },
1365
1346
  {
1366
1347
  "id": "frontend-nextjs-data-fetching",
@@ -1379,7 +1360,7 @@
1379
1360
  "server": "#server-component--tanstack-query-handoff",
1380
1361
  "client": "#usequery-hook-pattern"
1381
1362
  },
1382
- "digest": "Server Components do the initial load and call the .NET backend directly with BACKEND_API_PREFIX; Client Components never call the backend — they hit a same-origin proxy route with PROXY_API_PREFIX. Two named constants, never one shared API_PREFIX: the side that drifts breaks silently at runtime. Client data is TanStack Query 5: useQuery for reads, useMutation for writes, then invalidateQueries({ queryKey: userKeys.lists() }) (object syntax). Keys come from a per-feature key factory, never hardcoded ['users']. Every response is parsed with a Zod schema. QueryClient: staleTime 60s, retry 1, created once in useState; the Server Component passes initialData to the client hook. FORBIDDEN: useEffect(() => fetch…, []) and fetching a whole collection to page/sort/filter in the browser — page on the server, query in the URL."
1363
+ "digest": "Server Components do the initial load and call the .NET backend directly with BACKEND_API_PREFIX; Client Components never call the backend — they hit a same-origin proxy route with PROXY_API_PREFIX. Two named constants, never one shared API_PREFIX: the side that drifts breaks silently at runtime. Client data is TanStack Query 5: useQuery for reads, useMutation for writes, then invalidateQueries({ queryKey: userKeys.lists() }) (object syntax). Keys come from a per-feature key factory, never hardcoded ['users']. Every response is parsed with a Zod schema. QueryClient is created once in useState; the Server Component passes initialData to the client hook. FORBIDDEN: fetching a whole collection to page/sort/filter in the browser — page on the server, query in the URL. Fetch in useEffect and a DB driver in Next are lint errors (frontend-nextjs-lint)."
1383
1364
  },
1384
1365
  {
1385
1366
  "id": "frontend-nextjs-forms",
@@ -1420,7 +1401,7 @@
1420
1401
  "scroll": "#section-3-scroll-animations",
1421
1402
  "accessibility": "#section-7-wcag-accessibility-mandatory"
1422
1403
  },
1423
- "digest": "Component-level motion with Motion for React (the stack table's 'Framer Motion', its former name; motion 12+, current npm major 14): npm install motion and import from 'motion/react'. framer-motion is not deprecated (motion depends on it and re-exports it), but a codebase keeps one import path, motion/react, and never adds framer-motion alongside it. Variants live in motion/variants.ts (fadeIn, slideInUp/Down/Left/Right, scaleIn, staggerContainer, staggerItem, pageTransition) and take durations and easings from @/lib/design-tokens (fast 0.15, base 0.3); never hardcode duration numbers or easing arrays. Scroll reveals: whileInView with viewport once: true for simple cases, useInView for control. Page transitions: AnimatePresence mode='wait' keyed by usePathname(), subtle (y: 8), inside a 'use client' PageTransition provider -- the root app/layout.tsx stays a Server Component (no 'use client', keeps metadata) and just renders the provider. WCAG is mandatory: useReducedMotion() strips x/y/scale and keeps opacity, plus a prefers-reduced-motion CSS fallback. Skeletons use CSS animate-pulse, not motion. Limit motion to 3-5 moments per page. Scroll-driven scenes (ScrollTrigger, Lenis) belong to GSAP; never share an element with it."
1404
+ "digest": "Component-level motion with Motion for React (the stack table's 'Framer Motion', its former name; motion 12+, current npm major 14): npm install motion and import from 'motion/react'. framer-motion is not deprecated (motion depends on it and re-exports it), but a codebase keeps one import path, motion/react (a framer-motion import is a lint error, frontend-nextjs-lint). Variants live in motion/variants.ts (fadeIn, slideInUp/Down/Left/Right, scaleIn, staggerContainer, staggerItem, pageTransition) and take durations and easings from @/lib/design-tokens (fast 0.15, base 0.3); never hardcode duration numbers or easing arrays. Scroll reveals: whileInView with viewport once: true for simple cases, useInView for control. Page transitions: AnimatePresence mode='wait' keyed by usePathname(), inside a 'use client' PageTransition provider -- the root app/layout.tsx stays a Server Component (no 'use client', keeps metadata) and just renders the provider. WCAG is mandatory: useReducedMotion() strips x/y/scale and keeps opacity, plus a prefers-reduced-motion CSS fallback. Scroll-driven scenes (ScrollTrigger, Lenis) belong to GSAP; never share an element with it."
1424
1405
  },
1425
1406
  {
1426
1407
  "id": "frontend-nextjs-naming-conventions",
@@ -1439,7 +1420,7 @@
1439
1420
  "files": "#complete-reference-table",
1440
1421
  "components": "#complete-reference-table"
1441
1422
  },
1442
- "digest": "Next.js naming (prevents Linux case-sensitivity bugs). File names are ALWAYS kebab-case (`user-card.tsx`, never `UserCard.tsx`); exports are PascalCase for components (`UserCard`) and camelCase with a `use` prefix for hooks, whose files are kebab-case too (`use-users.ts` -> `useUsers`, `use-user.ts` for a single GET, `use-create-user.ts`, `use-update-user.ts`, `use-delete-user.ts`). Zod schema files end in `.schemas.ts` (`createUserSchema`) and type files in `.types.ts`, with types derived via `z.infer` instead of hand-written duplicates. Components use named exports (`export function UserCard`, not `export default`); `page.tsx` exports `default`. A feature `index.ts` re-exports. Never mix cases within a category (no `UserCard.tsx` next to `user-profile.tsx`)."
1423
+ "digest": "Next.js naming (prevents Linux case-sensitivity bugs). File names are ALWAYS kebab-case (`user-card.tsx`, never `UserCard.tsx`); components export PascalCase (`UserCard`); hook files are kebab-case too (`use-users.ts` -> `useUsers`, `use-user.ts` for a single GET, `use-create-user.ts`, `use-update-user.ts`, `use-delete-user.ts`). Zod schema files end in `.schemas.ts` (`createUserSchema`) and type files in `.types.ts`, with types derived via `z.infer` instead of hand-written duplicates. A feature `index.ts` re-exports. Named exports and the hook `use` prefix are lint errors, not prose (frontend-nextjs-lint). Never mix cases within a category (no `UserCard.tsx` next to `user-profile.tsx`)."
1443
1424
  },
1444
1425
  {
1445
1426
  "id": "frontend-nextjs-nextjs-patterns",
@@ -1458,7 +1439,7 @@
1458
1439
  "auth": "#route-handlers-bff-pattern",
1459
1440
  "caching": "#react-query--neon"
1460
1441
  },
1461
- "digest": "Next.js 15 App Router (not Pages Router): Server Components by default, `'use client'` only for state, effects, events or browser APIs; colocate `loading.tsx` and `error.tsx` (the latter MUST be `'use client'`) with `page.tsx`. Route Handlers act as a BFF: check `auth.getSession()` (401 if absent) and delegate to the .NET API; database access lives in the .NET backend via EF Core, NEVER in Next.js route handlers. Validate input with Zod on client and server (`safeParse`, 400). Fetch with TanStack Query (`useQuery`/`useMutation`, `staleTime: 60_000`, `retry: 1`, `invalidateQueries` on success), never `useEffect` for data fetching. Forms: react-hook-form + `zodResolver`. shadcn is copied via `npx shadcn@latest add`, not an npm dependency. Keep `\"strict\": true` plus `noUncheckedIndexedAccess` (agents self-correct from compiler errors); no `any` for API responses (Zod schema + `z.infer`)."
1442
+ "digest": "Next.js 15 App Router (not Pages Router): Server Components by default, `'use client'` only for state, effects, events or browser APIs; colocate `loading.tsx` and `error.tsx` with `page.tsx`. Route Handlers act as a BFF: check `auth.getSession()` (401 if absent) and delegate to the .NET API; database access lives in the .NET backend via EF Core (a DB driver in Next is a lint error, frontend-nextjs-lint). Validate input with Zod on client and server (`safeParse`, 400). Fetch with TanStack Query (`useQuery`/`useMutation`, `invalidateQueries` on success). Forms: react-hook-form + `zodResolver`. shadcn is copied via `npx shadcn@latest add`, not an npm dependency. Keep `\"strict\": true` plus `noUncheckedIndexedAccess` (agents self-correct from compiler errors); API responses are a Zod schema + `z.infer`."
1462
1443
  },
1463
1444
  {
1464
1445
  "id": "frontend-nextjs-project-structure",
@@ -1477,7 +1458,7 @@
1477
1458
  "structure": "#canonical-folder-tree",
1478
1459
  "organization": "#feature-index-pattern"
1479
1460
  },
1480
- "digest": "Next.js 15 App Router, src/ layout with the @/ alias, feature-based. src/app/ is routing only (layout, page, loading, error) and imports from features/, never the reverse. Each src/features/{feature-name}/ holds components/, hooks/ (TanStack Query hooks such as use-users.ts, use-create-user.ts), types/ (*.types.ts plus *.schemas.ts with Zod and z.infer) and an index.ts that is the explicit public API: import from '@/features/users', never from deep paths. NEVER import one feature from another (extract to components/ or lib/); components/ must not import features/, is presentation only with NO business logic, and components/ui/ is shadcn, never edited directly. Shared hooks/ make no API calls. lib/ holds api-client.ts (typed fetch wrapper for the .NET API) and query-client.tsx; types/api.ts has PaginatedResponse<T> and ApiError; env.mjs validates env vars with Zod."
1461
+ "digest": "Next.js 15 App Router, src/ layout with the @/ alias, feature-based. src/app/ is routing only (layout, page, loading, error); screen logic lives in features/. Each src/features/{feature-name}/ holds components/, hooks/ (TanStack Query hooks such as use-users.ts, use-create-user.ts), types/ (*.types.ts plus *.schemas.ts with Zod and z.infer) and an index.ts that is the explicit public API. NEVER import one feature from another (extract to components/ or lib/); components/ is presentation only with NO business logic, and components/ui/ is shadcn, never edited directly. Deep imports from outside a feature, components/ -> features/, features/ -> app/ and shared hooks/ -> the API client are lint errors (frontend-nextjs-lint). lib/ holds api-client.ts (typed fetch wrapper for the .NET API) and query-client.tsx; types/api.ts has PaginatedResponse<T> and ApiError; env.mjs validates env vars with Zod."
1481
1462
  },
1482
1463
  {
1483
1464
  "id": "frontend-nextjs-state-management",
@@ -1496,7 +1477,7 @@
1496
1477
  "patterns": "#state-decision-tree",
1497
1478
  "server": "#server-state-tanstack-query"
1498
1479
  },
1499
- "digest": "Server state (anything from the .NET API) lives in TanStack Query, never copied into `useState` via `useEffect`; derive state from server data instead of syncing it. Local UI state (modal open, form step, selected id) uses `useState`. A list's page, sort and filters, and a page's active tab, go in the URL (`searchParams`), never `useState`, so a refresh or shared link reopens the same view. Do not use React Context for server state (no caching or dedup); Context is for truly global theme, auth user or locale, and prop drilling 2-3 levels is fine. NEVER install Zustand or Redux before exhausting Server Components + TanStack Query. Zustand is justified only if ALL hold: state needed in 5+ unrelated components, complex update logic, Context causes measurable re-render problems, and the state is not from the server."
1480
+ "digest": "Server state (anything from the .NET API) lives in TanStack Query, never copied into `useState` via `useEffect`; derive state from server data instead of syncing it. Local UI state (modal open, form step, selected id) uses `useState`. A list's page, sort and filters, and a page's active tab, go in the URL (`searchParams`), never `useState`, so a refresh or shared link reopens the same view. Do not use React Context for server state (no caching or dedup); Context is for truly global theme, auth user or locale. Zustand/Redux imports are lint errors (frontend-nextjs-lint); the one exception is an eslint-disable-next-line with the reason in a single store file, only if ALL hold: state needed in 5+ unrelated components, complex update logic, Context causes measurable re-render problems, and the state is not from the server."
1500
1481
  },
1501
1482
  {
1502
1483
  "id": "frontend-nextjs-testing",
@@ -1830,7 +1811,7 @@
1830
1811
  "not-worth": "#o-que-não-merece",
1831
1812
  "size": "#tamanho"
1832
1813
  },
1833
- "digest": "Default is NO comment: code explains itself through names, types and tests. Before commenting, rename, extract a function or named constant, encode the state in the type, or write a test; comment only what none of these can express: an invariant the type does not express, an order that matters, an external quirk (third-party API, library bug with its link), or the contract of a package published for consumers outside the repo. Never process history (task, UC, ADR, FR, Gate, evaluator, spec.md, mockup: commit message and plan.md (## Decisões); in a test the link to the postcondition goes in the test NAME), never restate the code, no XML doc/JSDoc on application members that repeats the signature, no file headers, section banners, #region, commented-out code or loose TODOs, no claims about behavior (make them test assertions). One line is normal, ~3 is the ceiling; more than one comment line per ten code lines means the names are not doing the work. A heavily commented neighbour file is not a model. The comment-guard hook blocks an edit that ADDS process history, a banner/#region, a block over 3 lines or a doc-comment outside a published package; the verify validators node (code-budget) warns on added comments that cite process and on added comment density."
1814
+ "digest": "Default is NO comment: code explains itself through names, types and tests. Before commenting, rename, extract a function or named constant, encode the state in the type, or write a test; comment only what none of these can express: an invariant the type does not express, an order that matters, an external quirk (third-party API, library bug with its link), or the contract of a package published for consumers outside the repo. Never process history (task, UC, ADR, FR, Gate, evaluator, spec.md, mockup: commit message and plan.md (## Decisões); in a test the link to the postcondition goes in the test NAME), never restate the code, no XML doc/JSDoc on application members that repeats the signature, no file headers, section banners, #region, commented-out code or loose TODOs, no claims about behavior (make them test assertions). One line is normal, ~3 is the ceiling; more than one comment line per ten code lines means the names are not doing the work. A heavily commented neighbour file is not a model. The comment-guard hook blocks an edit that ADDS process history, a banner/#region, commented-out code, a block over 3 lines or a doc-comment outside a published package; the verify validators node (code-budget) warns on added comments that cite process and on added comment density."
1834
1815
  },
1835
1816
  {
1836
1817
  "id": "ai-agents-tool-adapters",
@@ -1926,6 +1907,28 @@
1926
1907
  "not-a-rule": "#o-que-não-é-regra"
1927
1908
  },
1928
1909
  "digest": "Rules with a reason that must never break are build errors, not prose: three files at the solution root, no .csproj change. Directory.Build.props adds Microsoft.CodeAnalysis.BannedApiAnalyzers 5.6.0 (PrivateAssets=all), BannedSymbols.txt as AdditionalFiles and morph.globalconfig as GlobalAnalyzerConfigFiles (with Central Package Management the version goes to Directory.Packages.props). BannedSymbols.txt bans M:System.Guid.NewGuid (use Guid.CreateVersion7(): time-ordered, does not fragment the Postgres PK index). morph.globalconfig sets RS0030 and CA2016 (a CancellationToken received and not forwarded) to error. A new banned API is one BannedSymbols.txt line with the reason after the semicolon, never a prose rule. sealed, primary constructors, var, expression bodies and using order are taste: neither prose nor analyzer; follow the neighbouring file."
1910
+ },
1911
+ {
1912
+ "id": "frontend-nextjs-lint",
1913
+ "name": "lint",
1914
+ "path": "frontend/nextjs/lint.md",
1915
+ "category": "frontend",
1916
+ "subcategory": "nextjs",
1917
+ "tags": [
1918
+ "frontend",
1919
+ "nextjs",
1920
+ "lint",
1921
+ "eslint"
1922
+ ],
1923
+ "stacks": [
1924
+ "nextjs"
1925
+ ],
1926
+ "anchors": {
1927
+ "files": "#arquivos",
1928
+ "rules": "#o-que-cada-regra-compra",
1929
+ "not-a-rule": "#o-que-não-é-regra"
1930
+ },
1931
+ "digest": "Rules with a reason that a machine can see are lint errors, not prose: one eslint.config.mjs at the Next app root, flat config spreading eslint-config-next/core-web-vitals and eslint-config-next/typescript, no new plugin. ESLint 9 (eslint-plugin-import/react peer range; the bundled typescript-eslint wants TypeScript <6.1); Next 16 next build no longer lints; the verify build node runs eslint --quiet on the files the feature changed (legacy lint debt never reddens a feature), from the app root, when eslint.config.* and eslint are present. no-restricted-imports bans framer-motion (one import path, motion/react), zustand/redux/@reduxjs/toolkit (server state is TanStack Query, page and filters in the URL), DB drivers pg/@neondatabase/serverless/@prisma/client/drizzle-orm/postgres (Next never touches the database; the .NET API does), the legacy shadcn @/components/ui/form, and @/features/*/* deep imports outside src/features (use the feature index.ts). import/no-restricted-paths: src/components never imports src/features, src/features never imports src/app, src/hooks never imports the API client (lib/api-client.ts, lib/api). no-restricted-syntax: fetch or apiClient called inside useEffect, and 'use client' in the root app/layout.tsx. import/no-default-export except Next convention files (page, layout, loading, error, not-found, template, default, route, sitemap, robots, icons, OG images) and *.config.*. @typescript-eslint/no-explicit-any error, no-console warn. A new restriction is one line in this file with the reason in its message; a one-off exception is eslint-disable-next-line with the reason. CSS class naming, animation class catalogs, optional className, Zod on props, motion counts and offsets, QueryClient defaults and prop-drilling depth are taste: neither prose nor lint."
1929
1932
  }
1930
1933
  ]
1931
1934
  }
@@ -819,17 +819,6 @@
819
819
  "verifiedAgainst": "CSS animations/transitions (language-level, no framework).",
820
820
  "provedBy": []
821
821
  },
822
- "frontend-design-system-naming": {
823
- "aliases": [
824
- "BEM",
825
- "CSS naming",
826
- "class naming",
827
- "design token naming",
828
- "component naming"
829
- ],
830
- "verifiedAgainst": "CSS (BEM + custom properties — language-level, no framework).",
831
- "provedBy": []
832
- },
833
822
  "frontend-design-system-premium-finish": {
834
823
  "aliases": [
835
824
  "premium finish",
@@ -1251,6 +1240,19 @@
1251
1240
  ],
1252
1241
  "verifiedAgainst": ".NET SDK 10.0.402, Microsoft.CodeAnalysis.BannedApiAnalyzers 5.6.0 (sonda: `Guid.NewGuid()` → erro RS0030, token não repassado → erro CA2016, código conforme → 0 erros).",
1253
1242
  "provedBy": []
1243
+ },
1244
+ "frontend-nextjs-lint": {
1245
+ "aliases": [
1246
+ "eslint",
1247
+ "eslint.config.mjs",
1248
+ "lint rule",
1249
+ "no-restricted-imports",
1250
+ "no-restricted-paths",
1251
+ "eslint-config-next",
1252
+ "default export"
1253
+ ],
1254
+ "verifiedAgainst": "eslint 9.39.5, eslint-config-next 16.4.2 (typescript-eslint 8.71.2, eslint-plugin-import 2.32.0), next 16.4.2, typescript 6.0.3 (sonda: um arquivo ruim por regra → erro da regra esperada, `no-console` → aviso, arquivos conformes e o scaffold `morph-spec scaffold nextjs` → 0 problemas).",
1255
+ "provedBy": []
1254
1256
  }
1255
1257
  },
1256
1258
  "sharedAliases": {
@@ -1323,10 +1325,6 @@
1323
1325
  "architecture-vertical-slice-vertical-slice",
1324
1326
  "backend-dotnet-vsa-handler-patterns"
1325
1327
  ],
1326
- "component naming": [
1327
- "frontend-design-system-naming",
1328
- "frontend-nextjs-naming-conventions"
1329
- ],
1330
1328
  "video generation": [
1331
1329
  "ai-agents-media-video",
1332
1330
  "frontend-design-system-ai-video-generation"
@@ -93,7 +93,7 @@ await upsert({ key: doc.sourceId, hash: sha256(doc.body) });
93
93
  ## Na revisão
94
94
 
95
95
  Na hora da edição, o hook `comment-guard` bloqueia o comentário **adicionado** que é história de
96
- processo, banner/`#region`, bloco de mais de 3 linhas ou `///`/JSDoc em projeto que não é pacote
96
+ processo, banner/`#region`, código comentado, bloco de mais de 3 linhas ou `///`/JSDoc em projeto que não é pacote
97
97
  publicado. Comentário que o arquivo já tinha nunca é acusado. Repetir o código em uma linha não é
98
98
  detectável por forma: fica com a revisão abaixo.
99
99
 
@@ -82,9 +82,7 @@ The page parses the query, renders the toolbar, and wraps the results in a `Susp
82
82
  // app/(admin)/products/page.tsx
83
83
  import { Suspense } from 'react';
84
84
  import { DataTableSkeleton } from '@/components/data-table-skeleton';
85
- import { ProductResults } from '@/features/products/components/product-results';
86
- import { ProductToolbar } from '@/features/products/components/product-toolbar';
87
- import { productListQuerySchema } from '@/features/products/types/product-list.schemas';
85
+ import { ProductResults, ProductToolbar, productListQuerySchema } from '@/features/products';
88
86
 
89
87
  export default async function ProductsPage({
90
88
  searchParams,
@@ -35,10 +35,14 @@ The page reads `tab` from `searchParams` and renders only that tab's content on
35
35
 
36
36
  ```tsx
37
37
  // app/(admin)/products/[id]/page.tsx
38
- import { ProductTabs, PRODUCT_TABS, type ProductTab } from '@/features/products/components/product-tabs';
39
- import { ProductDetailsTab } from '@/features/products/components/product-details-tab';
40
- import { ProductVariantsTab } from '@/features/products/components/product-variants-tab';
41
- import { ProductHistoryTab } from '@/features/products/components/product-history-tab';
38
+ import {
39
+ PRODUCT_TABS,
40
+ ProductDetailsTab,
41
+ ProductHistoryTab,
42
+ ProductTabs,
43
+ ProductVariantsTab,
44
+ type ProductTab,
45
+ } from '@/features/products';
42
46
 
43
47
  export default async function ProductPage({
44
48
  params,
@@ -16,9 +16,9 @@ Next.js App Router with TypeScript strict mode. Server Components by default, Cl
16
16
  - ALWAYS default to Server Components — add `'use client'` only when needed
17
17
  - ALWAYS keep `app/` directory for routing only — no business logic in page files
18
18
  - NEVER put data fetching in Client Components when a Server Component can do it
19
- - NEVER use `useEffect` to fetch data — use Server Components or TanStack Query
20
19
  - ALWAYS co-locate loading/error UI: `loading.tsx`, `error.tsx` next to `page.tsx`
21
20
  - ALWAYS use TypeScript — no `.js` or `.jsx` files in the project
21
+ - Fetch inside `useEffect` and `'use client'` in the root `layout.tsx` are lint errors (`frontend-nextjs-lint`)
22
22
 
23
23
  ## Server vs Client Components
24
24
 
@@ -68,7 +68,7 @@ src/app/
68
68
  | `page.tsx` | Route segment UI | Server Component (default) |
69
69
  | `layout.tsx` | Shared UI wrapper | Server Component |
70
70
  | `loading.tsx` | Suspense fallback | Server Component |
71
- | `error.tsx` | Error boundary | **Must be Client Component** |
71
+ | `error.tsx` | Error boundary | Client Component |
72
72
  | `not-found.tsx` | 404 page | Server Component |
73
73
  | `route.ts` | API route (avoid — use .NET API) | — |
74
74
 
@@ -80,7 +80,7 @@ the same-origin proxy prefix Client Components use. See `frontend/nextjs/data-fe
80
80
 
81
81
  ```tsx
82
82
  // app/(dashboard)/users/page.tsx
83
- import { UserList } from '@/features/users/components/user-list';
83
+ import { UserList } from '@/features/users';
84
84
  import { BACKEND_API_PREFIX } from '@/lib/api/prefixes'; // server-direct prefix ONLY
85
85
 
86
86
  async function getUsers(): Promise<User[]> {
@@ -123,7 +123,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
123
123
  | Wrong | Right | Why |
124
124
  |-------|-------|-----|
125
125
  | `'use client'` on every component | Only add when needed | Kills SSR benefits, bloats JS bundle |
126
- | `useEffect(() => { fetch(...) }, [])` | Server Component fetch or TanStack Query | Two renders, no caching, no SSR |
127
126
  | Business logic in `page.tsx` | Move to `features/` | pages are routes, not controllers |
128
127
  | `fetch` without error handling | Always check `res.ok` | Silent failures in production |
129
128
 
@@ -14,10 +14,10 @@ Three-tier component hierarchy. `components/ui/` is shadcn primitives (never edi
14
14
  ## Core Rules
15
15
 
16
16
  - NEVER edit files in `components/ui/` — they are regenerated by shadcn CLI
17
- - NEVER import from `features/` inside `components/` — components know nothing about domain
18
17
  - ALWAYS compose shadcn primitives in `components/` instead of editing them
19
18
  - ALWAYS add `'use client'` only if the component uses hooks, events, or browser APIs
20
19
  - NEVER pass raw API data directly to a component — transform to props first
20
+ - `components/` importing `features/` and the legacy `@/components/ui/form` are lint errors (`frontend-nextjs-lint`)
21
21
 
22
22
  ## Three-Tier Hierarchy
23
23
 
@@ -89,26 +89,13 @@ export function UsersPageHeader({ canInvite }: { canInvite: boolean }) {
89
89
 
90
90
  A feature **list** is a Tier 3 component too: the feature owns the columns and the URL ⇄ table mapping, the shared table owns the rendering — `admin-app/data-table.md` → "The Feature Table".
91
91
 
92
- ## Component Props Conventions
93
-
94
- ```tsx
95
- interface UserCardProps {
96
- user: User;
97
- onEdit?: (id: string) => void;
98
- className?: string; // Always allow className for Tailwind override
99
- }
100
-
101
- export function UserCard({ user, onEdit, className }: UserCardProps) {}
102
- ```
103
-
104
92
  ## Common Mistakes
105
93
 
106
94
  | Wrong | Right | Why |
107
95
  |-------|-------|-----|
108
96
  | Edit `components/ui/button.tsx` | Wrap in `components/action-button.tsx` | shadcn CLI overwrites it |
109
- | `import { useUsers } from '@/features/users'` in `components/` | Move to a feature component | Breaks tier isolation |
110
97
  | `'use client'` on all components | Only on interactive ones | Unnecessary client JS |
111
- | Pass raw fetch response as prop | Type and validate with Zod first | Runtime type safety |
98
+ | Pass raw fetch response as prop | Map it to typed props in the feature | The component stops depending on the API shape |
112
99
 
113
100
  ---
114
101
 
@@ -9,16 +9,16 @@
9
9
 
10
10
  ---
11
11
 
12
- Server Components for initial page load. TanStack Query for client mutations and interactive data. Never `useEffect` for fetching.
12
+ Server Components for initial page load. TanStack Query for client mutations and interactive data.
13
13
 
14
14
  ## Core Rules
15
15
 
16
16
  - ALWAYS use Server Components for initial data that doesn't require interactivity
17
17
  - ALWAYS use TanStack Query (`useQuery`, `useMutation`) for client-side data needs
18
- - NEVER use `useEffect(() => { fetch(...) }, [])` — this is the old pattern
19
18
  - ALWAYS validate API responses with Zod before using them
20
19
  - ALWAYS use query key factories for consistent cache invalidation
21
20
  - NEVER fetch a whole collection to page, sort or filter it in the browser — a list is paged on the server, with the query in the URL (`admin-app/data-table.md`)
21
+ - Fetch inside `useEffect` and a DB driver in Next are lint errors (`frontend-nextjs-lint`)
22
22
 
23
23
  ## TanStack Query Setup
24
24
 
@@ -134,7 +134,6 @@ export function UserListClient({ initialData }: { initialData: User[] }) {
134
134
 
135
135
  | Wrong | Right | Why |
136
136
  |-------|-------|-----|
137
- | `useEffect(() => { fetch(...) }, [])` | `useQuery(...)` | Two renders, no cache, no SSR |
138
137
  | No Zod on API response | `schema.parse(await res.json())` | Type safety at runtime |
139
138
  | Hardcoded query keys: `['users']` | Key factory: `userKeys.lists()` | Consistent invalidation |
140
139
  | `queryClient.invalidateQueries(['users'])` | `{ queryKey: userKeys.lists() }` | Object syntax required in v5 |
@@ -0,0 +1,141 @@
1
+ # ESLint Next.js — o lint cobra, a prosa não repete
2
+
3
+ > **Scope:** nextjs
4
+ > **Layer:** 0 (resumo injetado pela rule `nextjs-standards` (`**/*.ts`, `**/*.tsx`); o `stack-scan` acusa o projeto sem este arquivo)
5
+ > **Keywords:** eslint, lint, eslint.config.mjs, no-restricted-imports, no-restricted-paths, no-restricted-syntax, no-default-export, no-explicit-any, framer-motion, zustand
6
+ > **Load When:** projeto Next.js sem `eslint.config.mjs` com `no-restricted-imports`, ou o lint acusou uma destas regras
7
+
8
+ **Verified against:** eslint 9.39.5, eslint-config-next 16.4.2 (typescript-eslint 8.71.2, eslint-plugin-import 2.32.0), next 16.4.2, typescript 6.0.3 (sonda: um arquivo ruim por regra → erro da regra esperada, `no-console` → aviso, arquivos conformes e o scaffold `morph-spec scaffold nextjs` → 0 problemas). Last-verified: 2026-10-18.
9
+
10
+ ---
11
+
12
+ Regra de código que tem motivo e que uma máquina enxerga vira **erro de lint**, não frase que o
13
+ agente relê a cada sessão. Um arquivo na raiz do app Next basta, e só usa o que o
14
+ `eslint-config-next` já instala (typescript-eslint e eslint-plugin-import): nenhum plugin novo.
15
+
16
+ ## Arquivos
17
+
18
+ Dependências (`npm i -D eslint@9 eslint-config-next typescript`). ESLint 9 é a faixa que
19
+ `eslint-plugin-import` e `eslint-plugin-react` declaram como peer; o typescript-eslint do
20
+ `eslint-config-next` pede TypeScript `<6.1`.
21
+
22
+ No Next 16 o `next build` não roda mais o lint. O nó de build do `morph-spec verify` roda o
23
+ eslint (`--quiet`: só erro reprova) **só nos arquivos que a feature mudou**, na raiz do app, quando
24
+ acha `eslint.config.*` e o pacote `eslint` instalado: dívida de lint antiga nunca deixa a feature
25
+ vermelha. As zonas são relativas a essa raiz.
26
+
27
+ `eslint.config.mjs`:
28
+
29
+ ```js
30
+ import { defineConfig, globalIgnores } from 'eslint/config'
31
+ import nextVitals from 'eslint-config-next/core-web-vitals'
32
+ import nextTs from 'eslint-config-next/typescript'
33
+
34
+ const BANNED = [
35
+ [['framer-motion'], 'Motion entra por motion/react: um único caminho de import.'],
36
+ [['zustand', 'redux', '@reduxjs/toolkit'], 'Estado de servidor é do TanStack Query; página e filtro vão na URL.'],
37
+ [['pg', '@neondatabase/serverless', '@prisma/client', 'drizzle-orm', 'postgres'], 'O Next nunca fala com o banco: o dado vem da API .NET.'],
38
+ ]
39
+
40
+ const DEEP_FEATURE = {
41
+ group: ['@/features/*/*'],
42
+ message: 'Fora da feature, importe a API pública (@/features/x), nunca um caminho interno.',
43
+ }
44
+
45
+ const restrictedImports = (...extraPatterns) => ['error', {
46
+ paths: [
47
+ ...BANNED.flatMap(([names, message]) => names.map((name) => ({ name, message }))),
48
+ { name: '@/components/ui/form', message: 'Form legado do shadcn: Controller + Field (frontend-nextjs-forms).' },
49
+ ],
50
+ patterns: [
51
+ ...BANNED.map(([names, message]) => ({ group: names.map((n) => `${n}/*`), message })),
52
+ ...extraPatterns,
53
+ ],
54
+ }]
55
+
56
+ const FETCH_IN_EFFECT = {
57
+ selector:
58
+ "CallExpression[callee.name='useEffect'] CallExpression:matches([callee.name='fetch'], [callee.name='apiClient'], [callee.object.name='apiClient'])",
59
+ message: 'Dado do servidor vem de Server Component ou TanStack Query, nunca de fetch em useEffect.',
60
+ }
61
+
62
+ export default defineConfig([
63
+ ...nextVitals,
64
+ ...nextTs,
65
+ globalIgnores(['.next/**', 'out/**', 'build/**', 'next-env.d.ts']),
66
+ {
67
+ rules: {
68
+ 'no-restricted-imports': restrictedImports(DEEP_FEATURE),
69
+ 'import/no-restricted-paths': ['error', {
70
+ zones: [
71
+ { target: './src/components', from: './src/features', message: 'components/ é apresentação: não conhece feature.' },
72
+ { target: './src/features', from: './src/app', message: 'app/ importa de features/, nunca o contrário.' },
73
+ { target: './src/hooks', from: ['./src/lib/api-client.ts', './src/lib/api'], message: 'Hook compartilhado não chama API: isso é hook de feature.' },
74
+ ],
75
+ }],
76
+ 'no-restricted-syntax': ['error', FETCH_IN_EFFECT],
77
+ 'import/no-default-export': 'error',
78
+ '@typescript-eslint/no-explicit-any': 'error',
79
+ 'no-console': 'warn',
80
+ },
81
+ },
82
+ {
83
+ files: ['src/features/**'],
84
+ rules: { 'no-restricted-imports': restrictedImports() },
85
+ },
86
+ {
87
+ files: ['app/layout.tsx', 'src/app/layout.tsx'],
88
+ rules: {
89
+ 'no-restricted-syntax': ['error', FETCH_IN_EFFECT, {
90
+ selector: "Program > ExpressionStatement[directive='use client']",
91
+ message: "O layout raiz é Server Component (metadata): o 'use client' vai no provider que ele renderiza.",
92
+ }],
93
+ },
94
+ },
95
+ {
96
+ files: [
97
+ '**/app/**/{page,layout,loading,error,global-error,not-found,forbidden,unauthorized,template,default,route}.{ts,tsx}',
98
+ '**/app/**/{sitemap,robots,manifest,icon,apple-icon,opengraph-image,twitter-image}.{ts,tsx}',
99
+ '**/*.config.{js,mjs,cjs,ts}',
100
+ ],
101
+ rules: { 'import/no-default-export': 'off' },
102
+ },
103
+ ])
104
+ ```
105
+
106
+ Cada override de `no-restricted-imports` e `no-restricted-syntax` substitui a lista inteira da
107
+ regra naquele arquivo, por isso o bloco do layout repete `FETCH_IN_EFFECT` e o de `features/`
108
+ repete os pacotes banidos.
109
+
110
+ ## O que cada regra compra
111
+
112
+ | Regra | Bloqueia | Por quê |
113
+ |---|---|---|
114
+ | `no-restricted-imports` `@/features/*/*` (fora de `src/features/`) | `app/`, `components/` e `lib/` lendo arquivo interno de uma feature | o `index.ts` é a API pública: refatorar dentro da feature não quebra quem está fora |
115
+ | `no-restricted-imports` `framer-motion` | o segundo caminho de import do Motion | `motion/react` e `framer-motion` juntos duplicam o runtime no bundle e dividem o código em duas APIs |
116
+ | `no-restricted-imports` `zustand`, `redux`, `@reduxjs/toolkit` | store global | cópia do estado do servidor fica velha sem o cache do TanStack Query; página e filtro na URL sobrevivem a refresh e link |
117
+ | `no-restricted-imports` `pg`, `@neondatabase/serverless`, `@prisma/client`, `drizzle-orm`, `postgres` | driver de banco no Next | regra de negócio e acesso a dados moram no .NET (EF Core); um segundo caminho ao banco pula a autorização dele |
118
+ | `no-restricted-imports` `@/components/ui/form` | o `Form` legado do shadcn | a documentação do shadcn é escrita em `Controller` + `Field`; a tela legada migra quando é tocada |
119
+ | `import/no-restricted-paths` `src/components` ↛ `src/features` | componente compartilhado que conhece domínio | ele deixa de ser compartilhável e cria ciclo feature → components → feature |
120
+ | `import/no-restricted-paths` `src/features` ↛ `src/app` | feature importando rota | a dependência é de mão única: a rota compõe a feature |
121
+ | `import/no-restricted-paths` `src/hooks` ↛ cliente da API | hook compartilhado chamando o backend | chamada à API é de feature, com a key factory dela; hook compartilhado é só de UI |
122
+ | `no-restricted-syntax` fetch/`apiClient` dentro de `useEffect` | busca de dado no efeito | sem cache, sem dedup, corrida entre requisições e cascata de render; Server Component ou TanStack Query resolvem |
123
+ | `no-restricted-syntax` `'use client'` em `app/layout.tsx` | layout raiz no cliente | perde `metadata` e manda a árvore inteira para o bundle do cliente |
124
+ | `import/no-default-export` (exceto arquivos de rota e `*.config.*`) | `export default` em componente e hook | nome único em todo import: busca e rename acham todos os usos; o Next exige `default` só nos arquivos de convenção |
125
+ | `@typescript-eslint/no-explicit-any` | `any` | desliga o tipo no ponto onde o dado da API entra; o certo é schema Zod + `z.infer` |
126
+ | `no-console` (aviso) | `console.log` esquecido | ruído no console do usuário e dado vazando; aviso, porque o diagnóstico local é legítimo |
127
+
128
+ Restrição nova entra como linha neste arquivo, com o porquê na `message`. Nunca uma regra nova
129
+ em prosa. Exceção pontual é `// eslint-disable-next-line <regra> -- <motivo>` na linha, nunca o
130
+ desligamento da regra no projeto.
131
+
132
+ ## O que não é regra
133
+
134
+ Nome de classe CSS (BEM, prefixo de página, namespace `--app-`, sufixo de estado), catálogo de
135
+ classes `.animate-*`/`.stagger-*`, `className` opcional em toda prop, Zod em props, quantos
136
+ momentos de motion uma página tem, o deslocamento de uma transição, o skeleton com
137
+ `animate-pulse`, os defaults de `staleTime`/`retry` do `QueryClient` e quantos níveis de prop
138
+ drilling são aceitáveis são gosto: nem prosa nem lint. Siga o arquivo vizinho, e o formatter do
139
+ projeto decide a forma. Também saíram da prosa o `'use client'` no `error.tsx` (o `next build`
140
+ recusa sem ele) e o prefixo `use` do hook (o `react-hooks/rules-of-hooks` que vem no
141
+ `eslint-config-next` acusa hook chamado fora de função `useX`).
@@ -221,14 +221,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
221
221
 
222
222
  - `mode="wait"` ensures the exit animation completes before the entering page renders.
223
223
  - The `key` prop must change on navigation — use `usePathname()` from `next/navigation`.
224
- - Keep page transitions subtle (`y: 8`, fast duration) — aggressive transitions hurt perceived performance.
225
224
 
226
225
  ---
227
226
 
228
227
  ## Section 6: Loading Patterns
229
228
 
230
- Prefer CSS `animate-pulse` for skeleton loaders over Motion — CSS animations run off the main thread and have lower performance overhead.
231
-
232
229
  ```tsx
233
230
  // components/skeleton.tsx
234
231
  import { cn } from '@/lib/utils';
@@ -315,11 +312,8 @@ This CSS fallback catches animations outside Motion (Tailwind `animate-*`, third
315
312
  | `duration: 0.8` for UI interactions | `duration: durations.fast` (0.15) for micro-interactions |
316
313
  | Hardcoded easing `[0.4, 0, 0.2, 1]` | `ease: easings.standard` (from design-tokens) |
317
314
  | No `useReducedMotion()` check | Always check or use CSS media query |
318
- | Animating everything on the page | Reserve motion for 3-5 meaningful moments per page |
319
315
  | Forgetting `animation-fill-mode: backwards` in stagger | Will cause a flash before the animation starts |
320
316
  | `AnimatePresence` without `key` changes | Exit animations will never fire |
321
- | Using `motion` for skeleton loaders | Use CSS `animate-pulse` — lower performance cost |
322
- | `npm install framer-motion` / `import ... from 'framer-motion'` | `npm install motion` / `import ... from 'motion/react'` — same code underneath, but one import path per codebase |
323
317
  | Need ScrollTrigger, SVG morph, or complex timelines | Use GSAP — see `frontend/gsap/gsap-core.md` |
324
318
 
325
319
  ---
@@ -333,7 +327,8 @@ npm install motion
333
327
  > The package is `motion` and components import from `motion/react`. `framer-motion`
334
328
  > is not deprecated: it is published with the same version and `motion` depends on
335
329
  > it. New code still depends on `motion` and imports from `motion/react`, so the
336
- > codebase has a single import path; do not add `framer-motion` alongside it.
330
+ > codebase has a single import path; a `framer-motion` import is a lint error
331
+ > (`frontend-nextjs-lint`).
337
332
 
338
333
  Verify the installed version supports the features used here (requires motion >= 11; current major on npm is 14):
339
334
 
@@ -15,9 +15,9 @@ Consistent naming prevents filesystem bugs (Linux case-sensitivity) and keeps th
15
15
 
16
16
  - ALWAYS use kebab-case for file names — never PascalCase or camelCase
17
17
  - ALWAYS use PascalCase for React component exports
18
- - ALWAYS use camelCase starting with `use` for hook exports
19
18
  - ALWAYS suffix schema files with `.schemas.ts` and type files with `.types.ts`
20
19
  - NEVER mix cases in the same category — no `UserCard.tsx` alongside `user-profile.tsx`
20
+ - Named exports, the hook `use` prefix and `default` only in route files are lint errors (`frontend-nextjs-lint`), not rules to reread
21
21
 
22
22
  ## Complete Reference Table
23
23
 
@@ -63,7 +63,6 @@ export type UpdateUserInput = z.infer<typeof updateUserSchema>;
63
63
  |-------|-------|-----|
64
64
  | `UserCard.tsx` | `user-card.tsx` | Linux servers are case-sensitive |
65
65
  | `useUsers.ts` | `use-users.ts` | Inconsistent with Next.js file conventions |
66
- | `export default function UserCard` | `export function UserCard` | Named exports are more refactor-safe |
67
66
  | `type User = { id: string }` | `type User = z.infer<typeof userSchema>` | Single source of truth |
68
67
 
69
68
  ---
@@ -16,7 +16,8 @@ Stack: Next.js 15 + Neon PostgreSQL + .NET Backend
16
16
  - ALWAYS use App Router (not Pages Router)
17
17
  - Default to Server Components -- add `'use client'` only when needed
18
18
  - ALWAYS colocate loading.tsx and error.tsx with page.tsx
19
- - NEVER call the database directly from client -- use Route Handlers as BFF
19
+ - Route Handlers are a BFF that delegates to the .NET API
20
+ - A DB driver, `any`, fetch inside `useEffect` and `'use client'` in the root layout are lint errors (`frontend-nextjs-lint`)
20
21
  - ALWAYS validate inputs with Zod on both client and server
21
22
  - Use TypeScript strict mode (`"strict": true`)
22
23
 
@@ -58,7 +59,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
58
59
  // loading.tsx
59
60
  export default function Loading() { return <div className="animate-pulse">Loading...</div>; }
60
61
 
61
- // error.tsx — MUST be 'use client'
62
+ // error.tsx
62
63
  'use client';
63
64
  export default function Error({ error, reset }: { error: Error; reset: () => void }) {
64
65
  return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
@@ -187,7 +188,7 @@ export function DocumentForm() {
187
188
 
188
189
  ## shadcn/ui
189
190
 
190
- Install: `npx shadcn@latest init` then `npx shadcn@latest add button input card dialog form`.
191
+ Install: `npx shadcn@latest init` then `npx shadcn@latest add button input card dialog field`.
191
192
  Components are copied to `components/ui/` -- NOT an npm dependency, your code to customize.
192
193
 
193
194
  ## TypeScript Strict Patterns
@@ -195,7 +196,7 @@ Components are copied to `components/ui/` -- NOT an npm dependency, your code to
195
196
  | Pattern | Approach |
196
197
  |---------|----------|
197
198
  | API responses | Zod schema + `z.infer<typeof schema>` |
198
- | Props | Explicit interface, no `any` |
199
+ | Props | Explicit interface |
199
200
  | Event handlers | `React.ChangeEvent<HTMLInputElement>` |
200
201
  | Null safety | `?.` over type assertions, `if (!data) return null` |
201
202
 
@@ -204,10 +205,7 @@ Components are copied to `components/ui/` -- NOT an npm dependency, your code to
204
205
  | Wrong | Right | Why |
205
206
  |-------|-------|-----|
206
207
  | `'use client'` on every component | Default to Server Components | Unnecessary JS bundle size |
207
- | Direct DB from client | Route Handler `/api/*` as BFF | Exposes queries, harder to secure |
208
- | `any` for API responses | Zod schema + infer | No runtime safety |
209
208
  | Missing loading.tsx | Colocate with page.tsx | Blank page during load |
210
- | `useEffect` for data fetching | React Query `useQuery` | No caching, race conditions |
211
209
  | Form validation on submit only | Zod resolver + react-hook-form | Delayed error feedback |
212
210
  | shadcn as npm package | `npx shadcn@latest add` | Copy-paste system, not a dependency |
213
211