Skip to content

feat: add feature related configuration#261

Open
huang-julien wants to merge 2 commits intomainfrom
feat/features_conf
Open

feat: add feature related configuration#261
huang-julien wants to merge 2 commits intomainfrom
feat/features_conf

Conversation

@huang-julien
Copy link
Member

@huang-julien huang-julien commented Mar 5, 2026

🔗 Linked issue

📚 Description

This PR adds configuration related to features themselves. Allows for example to users to ignore some domains in wbevitals.


AI

This pull request introduces per-feature configuration options for Nuxt Hints features, allowing each feature to accept its own options object in addition to simple boolean or flag settings. It refactors the feature flag types, updates feature detection logic to handle these new options, and adds support for customizing behavior in the htmlValidate, thirdPartyScripts, and webVitals features via user-provided options. The changes also include dependency updates and code improvements for clarity and maintainability.

Feature configuration and type system refactor:

  • Refactored feature flag types in src/runtime/core/types.ts to allow each feature to be configured as a boolean or with a FeatureFlags object containing logs, devtools, and an options field for per-feature options. Added FeatureOptionsMap for strong typing of feature-specific options. [1] [2]
  • Updated all feature detection functions (isFeatureDevtoolsEnabled, isFeatureLogsEnabled, etc.) to treat devtools and logs as enabled unless explicitly set to false, and to properly handle the new options structure. [1] [2] [3]

Per-feature options support and usage:

  • Added getFeatureOptions helper functions to extract per-feature options from configuration, used throughout the codebase for type-safe access to options. [1] [2]
  • Implemented usage of per-feature options in feature plugins:

Dependency and configuration updates:

  • Added defu as a dependency for deep merging of configuration objects in package.json and pnpm-lock.yaml. [1] [2]

General code improvements:

  • Improved code clarity, error handling, and maintainability in feature plugins by refactoring logic and using constants for formats and schemes. [1] [2]

Type and interface additions:

  • Added new types/interfaces for per-feature options in src/runtime/feature-options.ts, src/runtime/html-validate/types.ts, src/runtime/third-party-scripts/types.ts, and src/runtime/web-vitals/types.ts to ensure type safety and extensibility. [1] [2] [3] [4]

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 5, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/hints@261

commit: 8d016da

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Changes add a feature-options system and type redesign enabling per-feature option objects. A new generic FeatureFlags<T> and mapped Features type are introduced; ModuleOptions.features and runtime typings are updated to use Features. New FeatureOptionsMap and specific option types for htmlValidate, thirdPartyScripts, and webVitals are added. Runtime helpers gain getFeatureOptions and now treat object devtools/logs flags as enabled unless explicitly false. Plugins for HTML validation, third-party scripts, and web-vitals are updated to read and merge feature options. defu is added as a dependency. Unit tests for feature helpers were added.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add feature related configuration' is related to the main objective of the PR, which adds per-feature configuration options, but is somewhat generic and could be more specific.
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the motivation (allowing users to ignore domains in webvitals), the implementation approach (per-feature options, type refactoring), and the affected features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/features_conf

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/core/features.ts`:
- Around line 7-11: The object-type checks access properties without guarding
against null; update the conditions in isFeatureLogsEnabled and the analogous
function isFeatureDevtoolsEnabled (and any similar checks in this file) to first
ensure the feature value is not null before testing typeof (e.g., use
features[feature] !== null && typeof features[feature] === 'object' or
features[feature] != null && typeof ...), then read .logs or .devtools as before
so the expression preserves the original boolean semantics but avoids a runtime
crash when a feature is null.

In `@src/runtime/core/types.ts`:
- Line 5: The generic type bound for FeatureFlags uses an explicit any which
violates lint; update the declaration of FeatureFlags so the generic constraint
uses unknown instead of any (e.g., change T extends Record<string, any> to T
extends Record<string, unknown> and keep the default as Record<string, never>)
so that the type remains equivalent but avoids
`@typescript-eslint/no-explicit-any`; modify the FeatureFlags<T ...> declaration
accordingly.

In `@src/runtime/html-validate/nitro.plugin.ts`:
- Line 33: Remove the trailing whitespace after the object passed to defu when
creating opts (the const opts: ConfigData = defu({...}) expression) so the code
contains no trailing spaces; update the line in the nitro.plugin.ts file (around
the defu call that initializes opts) to eliminate the extra space at the end of
the line to satisfy CI style checks.
- Around line 33-36: The options are being merged with defu in the wrong order
so user options from getFeatureOptions('htmlValidate') are shadowed by
DEFAULT_EXTENDS/DEFAULT_RULES; change the merge to pass the user options
(getFeatureOptions('htmlValidate') ?? {}) as the first argument and the defaults
({ extends: DEFAULT_EXTENDS, rules: DEFAULT_RULES }) as the subsequent argument
so that user-provided values override the defaults when building opts
(ConfigData) using defu.

In `@src/runtime/third-party-scripts/plugin.client.ts`:
- Around line 9-10: The function buildExtensionSchemesRegex is vulnerable
because it interpolates raw scheme strings into a RegExp; map the input schemes
through a regex-escaping routine (e.g., escape any -\/\^$*+?.()|[]{} characters)
before joining, then build the RegExp from the escaped strings so metacharacters
in configured schemes cannot break or change the pattern; add a small helper
named escapeRegExp and use it when constructing the regex in
buildExtensionSchemesRegex.
- Line 5: Remove the unused type import ThirdPartyScriptsFeatureOptions from
plugin.client.ts; locate the import statement "import type {
ThirdPartyScriptsFeatureOptions } from './types'" and delete it (or remove
ThirdPartyScriptsFeatureOptions from the import list) so the file no longer
references that unused symbol and the linter/CI error is resolved.

In `@src/runtime/types.d.ts`:
- Line 5: Remove the unused type import FeaturesName from the import statement:
update the import that currently reads "import type { FeaturesName, Features }
from './core/types'" to only import Features (i.e., "import type { Features }
from './core/types'") so the unused symbol FeaturesName is no longer imported
and the lint error is resolved.

In `@src/runtime/web-vitals/plugin.client.ts`:
- Around line 162-167: Inside the onCLS callback loop that iterates CLS
performance entries, don't use return when an entry is missing sources or when
sourceElement is absent/ignored; replace those early returns with continue so
you skip only the current performanceEntry and continue processing remaining
entries (look for variables/perimeter names performanceEntry, sourceElement and
the onCLS callback loop in plugin.client.ts).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08e9c8ac-7209-4ac2-86c7-dd2ccf58f313

📥 Commits

Reviewing files that changed from the base of the PR and between ff5a66a and 4a9ec03.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • client/app/composables/features.ts
  • package.json
  • src/features.ts
  • src/module.ts
  • src/runtime/core/features.ts
  • src/runtime/core/types.ts
  • src/runtime/feature-options.ts
  • src/runtime/html-validate/nitro.plugin.ts
  • src/runtime/html-validate/types.ts
  • src/runtime/third-party-scripts/plugin.client.ts
  • src/runtime/third-party-scripts/types.ts
  • src/runtime/types.d.ts
  • src/runtime/web-vitals/plugin.client.ts
  • src/runtime/web-vitals/types.ts

Comment on lines +7 to +11
return typeof features[feature] === 'object' ? features[feature].devtools !== false : !!features[feature]
}

export function isFeatureLogsEnabled(feature: FeaturesName): boolean {
return typeof features[feature] === 'object' ? features[feature].logs : !!features[feature]
return typeof features[feature] === 'object' ? features[feature].logs !== false : !!features[feature]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard object checks against null before property access.

Lines 7, 11, and 20 use typeof value === 'object' and then access fields. If a feature is null, this will throw at runtime.

Suggested fix
 export function isFeatureDevtoolsEnabled(feature: FeaturesName): boolean {
-  return typeof features[feature] === 'object' ? features[feature].devtools !== false : !!features[feature]
+  const value = features[feature]
+  return value && typeof value === 'object' ? value.devtools !== false : !!value
 }
 
 export function isFeatureLogsEnabled(feature: FeaturesName): boolean {
-  return typeof features[feature] === 'object' ? features[feature].logs !== false : !!features[feature]
+  const value = features[feature]
+  return value && typeof value === 'object' ? value.logs !== false : !!value
 }
@@
 export function getFeatureOptions<K extends keyof FeatureOptionsMap>(feature: K): FeatureOptionsMap[K] | undefined {
   const val = features[feature]
-  if (typeof val === 'object' && val.options) {
+  if (val && typeof val === 'object' && val.options) {
     return val.options as FeatureOptionsMap[K]
   }
   return undefined
 }

Also applies to: 18-23

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/core/features.ts` around lines 7 - 11, The object-type checks
access properties without guarding against null; update the conditions in
isFeatureLogsEnabled and the analogous function isFeatureDevtoolsEnabled (and
any similar checks in this file) to first ensure the feature value is not null
before testing typeof (e.g., use features[feature] !== null && typeof
features[feature] === 'object' or features[feature] != null && typeof ...), then
read .logs or .devtools as before so the expression preserves the original
boolean semantics but avoids a runtime crash when a feature is null.


export type FeaturesName = 'hydration' | 'lazyLoad' | 'webVitals' | 'thirdPartyScripts' | 'htmlValidate'

export type FeatureFlags<T extends Record<string, any> = Record<string, never>> = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace explicit any in generic bound to fix lint failure.

Line 5 violates @typescript-eslint/no-explicit-any, which is currently failing CI.

Suggested fix
-export type FeatureFlags<T extends Record<string, any> = Record<string, never>> = {
+export type FeatureFlags<T extends Record<string, unknown> = Record<string, never>> = {
📝 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.

Suggested change
export type FeatureFlags<T extends Record<string, any> = Record<string, never>> = {
export type FeatureFlags<T extends Record<string, unknown> = Record<string, never>> = {
🧰 Tools
🪛 GitHub Actions: ci

[error] 5-5: eslint error: Unexpected any. Specify a different type (@typescript-eslint/no-explicit-any)

🪛 GitHub Check: ci

[failure] 5-5:
Unexpected any. Specify a different type

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/core/types.ts` at line 5, The generic type bound for FeatureFlags
uses an explicit any which violates lint; update the declaration of FeatureFlags
so the generic constraint uses unknown instead of any (e.g., change T extends
Record<string, any> to T extends Record<string, unknown> and keep the default as
Record<string, never>) so that the type remains equivalent but avoids
`@typescript-eslint/no-explicit-any`; modify the FeatureFlags<T ...> declaration
accordingly.

'no-inline-style': 'off',
},
})
const opts: ConfigData = defu({
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove trailing whitespace on Line 33 to clear CI style checks.

The current CI output flags trailing spaces here.

🧰 Tools
🪛 GitHub Check: ci

[failure] 33-33:
Trailing spaces not allowed

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/html-validate/nitro.plugin.ts` at line 33, Remove the trailing
whitespace after the object passed to defu when creating opts (the const opts:
ConfigData = defu({...}) expression) so the code contains no trailing spaces;
update the line in the nitro.plugin.ts file (around the defu call that
initializes opts) to eliminate the extra space at the end of the line to satisfy
CI style checks.

Comment on lines +33 to +36
const opts: ConfigData = defu({
extends: DEFAULT_EXTENDS,
rules: DEFAULT_RULES,
}, getFeatureOptions('htmlValidate') ?? {})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

defu npm package function argument precedence which argument has priority

💡 Result:

In defu(object, ...defaults), the leftmost argument wins.

Priority order (highest → lowest):

  1. object (first argument)
  2. first item in defaults
  3. next item in defaults
  4. … last default (lowest)

So if the same key exists in multiple arguments, the earliest (leftmost) value is kept, and later arguments only fill in missing properties. [1]

Example: defu({ a: 2 }, { a: 1 }, { a: 0 }){ a: 2 } (because the first arg has priority). [1]

Source: UnJS defu docs. [1]

References:
[1] (unjs.pages.dev)

Citations:


🏁 Script executed:

find . -name "nitro.plugin.ts" -type f

Repository: nuxt/hints

Length of output: 227


🏁 Script executed:

cat -n ./src/runtime/html-validate/nitro.plugin.ts | head -50

Repository: nuxt/hints

Length of output: 2154


User htmlValidate options are shadowed by defaults due to defu argument order.

In defu(object, ...defaults), the first argument has priority. Currently, defaults are passed first, preventing user-provided options from getFeatureOptions('htmlValidate') from overriding them. This prevents customization of validation rules and extended configurations.

Suggested fix
-  const opts: ConfigData = defu({ 
-    extends: DEFAULT_EXTENDS,
-    rules: DEFAULT_RULES,
-  }, getFeatureOptions('htmlValidate') ?? {})
+  const opts: ConfigData = defu(getFeatureOptions('htmlValidate') ?? {}, {
+    extends: DEFAULT_EXTENDS,
+    rules: DEFAULT_RULES,
+  })
📝 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.

Suggested change
const opts: ConfigData = defu({
extends: DEFAULT_EXTENDS,
rules: DEFAULT_RULES,
}, getFeatureOptions('htmlValidate') ?? {})
const opts: ConfigData = defu(getFeatureOptions('htmlValidate') ?? {}, {
extends: DEFAULT_EXTENDS,
rules: DEFAULT_RULES,
})
🧰 Tools
🪛 GitHub Check: ci

[failure] 33-33:
Trailing spaces not allowed

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/html-validate/nitro.plugin.ts` around lines 33 - 36, The options
are being merged with defu in the wrong order so user options from
getFeatureOptions('htmlValidate') are shadowed by DEFAULT_EXTENDS/DEFAULT_RULES;
change the merge to pass the user options (getFeatureOptions('htmlValidate') ??
{}) as the first argument and the defaults ({ extends: DEFAULT_EXTENDS, rules:
DEFAULT_RULES }) as the subsequent argument so that user-provided values
override the defaults when building opts (ConfigData) using defu.

import { defu } from 'defu'
import { logger } from './utils'
import { getFeatureOptions } from '../core/features'
import type { ThirdPartyScriptsFeatureOptions } from './types'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove unused ThirdPartyScriptsFeatureOptions import to unblock CI.

Line 5 is currently unused and reported by the lint job.

Suggested fix
-import type { ThirdPartyScriptsFeatureOptions } from './types'
📝 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.

Suggested change
import type { ThirdPartyScriptsFeatureOptions } from './types'
🧰 Tools
🪛 GitHub Check: ci

[failure] 5-5:
'ThirdPartyScriptsFeatureOptions' is defined but never used. Allowed unused vars must match /^_/u

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/third-party-scripts/plugin.client.ts` at line 5, Remove the
unused type import ThirdPartyScriptsFeatureOptions from plugin.client.ts; locate
the import statement "import type { ThirdPartyScriptsFeatureOptions } from
'./types'" and delete it (or remove ThirdPartyScriptsFeatureOptions from the
import list) so the file no longer references that unused symbol and the
linter/CI error is resolved.

Comment on lines +9 to +10
function buildExtensionSchemesRegex(schemes: string[]) {
return new RegExp(`^(${schemes.join('|')}):`)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Escape configured schemes before interpolating into RegExp.

Lines 9–10 directly join config values into regex source. A scheme containing regex metacharacters can produce invalid patterns or unintended matches.

Suggested fix
+function escapeRegex(source: string): string {
+  return source.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+}
+
 function buildExtensionSchemesRegex(schemes: string[]) {
-  return new RegExp(`^(${schemes.join('|')}):`)
+  return new RegExp(`^(${schemes.map(escapeRegex).join('|')}):`)
 }
🧰 Tools
🪛 ast-grep (0.41.0)

[warning] 9-9: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^(${schemes.join('|')}):)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/third-party-scripts/plugin.client.ts` around lines 9 - 10, The
function buildExtensionSchemesRegex is vulnerable because it interpolates raw
scheme strings into a RegExp; map the input schemes through a regex-escaping
routine (e.g., escape any -\/\^$*+?.()|[]{} characters) before joining, then
build the RegExp from the escaped strings so metacharacters in configured
schemes cannot break or change the pattern; add a small helper named
escapeRegExp and use it when constructing the regex in
buildExtensionSchemesRegex.

import type { HydrationMismatchPayload, LocalHydrationMismatch } from './hydration/types'
import type { DirectImportInfo, LazyHydrationState } from './lazy-load/composables'
import type { FeaturesName, FeatureFlags } from './core/types'
import type { FeaturesName, Features } from './core/types'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove unused FeaturesName import to satisfy lint gate.

FeaturesName on Line 5 is never referenced, and CI already reports this as a failure.

Suggested fix
-import type { FeaturesName, Features } from './core/types'
+import type { Features } from './core/types'
📝 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.

Suggested change
import type { FeaturesName, Features } from './core/types'
import type { Features } from './core/types'
🧰 Tools
🪛 GitHub Check: ci

[failure] 5-5:
'FeaturesName' is defined but never used. Allowed unused vars must match /^_/u

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/types.d.ts` at line 5, Remove the unused type import FeaturesName
from the import statement: update the import that currently reads "import type {
FeaturesName, Features } from './core/types'" to only import Features (i.e.,
"import type { Features } from './core/types'") so the unused symbol
FeaturesName is no longer imported and the lint error is resolved.

Comment on lines +162 to +167
if (!performanceEntry.sources?.[0]) return

logger.info(
'[performance] Potential CLS Element: ',
sourceElement,
)
const sourceElement = performanceEntry.sources?.[0].node

if ((performanceEntry.value ?? 0) > 0.1) {
logger.warn(
`[performance] CLS was ${performanceEntry.value}. Good result is below 0.1 \n\n Learn more: https://web.dev/articles/cls#what-is-a-good-cls-score`,
)
}
// Nuxt DevTools button causes small layout shift so we ignore it
if (!sourceElement || sourceElement.parentElement?.className.includes('nuxt-devtools')) return
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use continue instead of return inside the CLS entries loop.

On Lines 162 and 167, return exits the entire onCLS callback, so one incomplete/ignored entry prevents processing remaining entries.

Suggested fix
-            if (!performanceEntry.sources?.[0]) return
+            if (!performanceEntry.sources?.[0]) continue
@@
-            if (!sourceElement || sourceElement.parentElement?.className.includes('nuxt-devtools')) return
+            if (!sourceElement || sourceElement.parentElement?.className.includes('nuxt-devtools')) continue
📝 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.

Suggested change
if (!performanceEntry.sources?.[0]) return
logger.info(
'[performance] Potential CLS Element: ',
sourceElement,
)
const sourceElement = performanceEntry.sources?.[0].node
if ((performanceEntry.value ?? 0) > 0.1) {
logger.warn(
`[performance] CLS was ${performanceEntry.value}. Good result is below 0.1 \n\n Learn more: https://web.dev/articles/cls#what-is-a-good-cls-score`,
)
}
// Nuxt DevTools button causes small layout shift so we ignore it
if (!sourceElement || sourceElement.parentElement?.className.includes('nuxt-devtools')) return
if (!performanceEntry.sources?.[0]) continue
const sourceElement = performanceEntry.sources?.[0].node
// Nuxt DevTools button causes small layout shift so we ignore it
if (!sourceElement || sourceElement.parentElement?.className.includes('nuxt-devtools')) continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/web-vitals/plugin.client.ts` around lines 162 - 167, Inside the
onCLS callback loop that iterates CLS performance entries, don't use return when
an entry is missing sources or when sourceElement is absent/ignored; replace
those early returns with continue so you skip only the current performanceEntry
and continue processing remaining entries (look for variables/perimeter names
performanceEntry, sourceElement and the onCLS callback loop in
plugin.client.ts).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/unit/build-features.test.ts (1)

45-90: Add build-time isFeatureLogsEnabled parity tests.

This suite validates devtools defaults well, but logs behavior for build-time helpers is currently untested. Adding the same matrix here would reduce regression risk.

✅ Suggested test addition
-import { isFeatureDevtoolsEnabled, isFeatureEnabled, getFeatureOptions } from '../../src/features'
+import { isFeatureDevtoolsEnabled, isFeatureEnabled, isFeatureLogsEnabled, getFeatureOptions } from '../../src/features'
...
+  describe('isFeatureLogsEnabled', () => {
+    it('returns true for a boolean-enabled feature', () => {
+      const opts = makeOptions({
+        hydration: true,
+        lazyLoad: true,
+        webVitals: true,
+        thirdPartyScripts: true,
+        htmlValidate: true,
+      })
+      expect(isFeatureLogsEnabled(opts, 'hydration')).toBe(true)
+    })
+
+    it('returns false for a boolean-disabled feature', () => {
+      const opts = makeOptions({
+        hydration: false,
+        lazyLoad: true,
+        webVitals: true,
+        thirdPartyScripts: true,
+        htmlValidate: true,
+      })
+      expect(isFeatureLogsEnabled(opts, 'hydration')).toBe(false)
+    })
+
+    it('reads logs flag from object config and defaults to true when omitted', () => {
+      const opts = makeOptions({
+        hydration: true,
+        lazyLoad: true,
+        webVitals: { logs: false },
+        thirdPartyScripts: { logs: true },
+        htmlValidate: { devtools: true },
+      })
+      expect(isFeatureLogsEnabled(opts, 'webVitals')).toBe(false)
+      expect(isFeatureLogsEnabled(opts, 'thirdPartyScripts')).toBe(true)
+      expect(isFeatureLogsEnabled(opts, 'htmlValidate')).toBe(true)
+    })
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/build-features.test.ts` around lines 45 - 90, Add unit tests for
parity between devtools and logs by creating a new describe block for
isFeatureLogsEnabled that mirrors the existing isFeatureDevtoolsEnabled cases:
1) assert true when the feature is boolean-enabled, 2) assert false when
boolean-disabled, 3) read logs flag from object config (e.g., webVitals: { logs:
false } and thirdPartyScripts: { logs: true }), and 4) default logs to true when
not specified in an object (e.g., webVitals: { devtools: false, logs: undefined
} or only other flags present). Use the same test helper makeOptions and call
isFeatureLogsEnabled(opts, '<featureName>') in each test to match the existing
test matrix for isFeatureDevtoolsEnabled.
test/unit/core/features.test.ts (1)

91-109: Add a boolean-feature getFeatureOptions undefined case.

You already test object-without-options; adding the boolean path makes the runtime contract fully explicit.

✅ Suggested test addition
   describe('getFeatureOptions', () => {
@@
     it('returns undefined for a feature with no options', () => {
       expect(getFeatureOptions('htmlValidate')).toBeUndefined()
     })
+
+    it('returns undefined when feature is configured as a boolean', () => {
+      expect(getFeatureOptions('hydration')).toBeUndefined()
+    })
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/core/features.test.ts` around lines 91 - 109, Add a test case to
cover the boolean-feature path by calling getFeatureOptions with a feature key
that is configured as a boolean and asserting it returns undefined;
specifically, inside the describe('getFeatureOptions') block add an it('returns
undefined for a boolean feature') that calls
getFeatureOptions('FEATURE_KEY_THAT_IS_BOOLEAN') and expects .toBeUndefined(),
replacing FEATURE_KEY_THAT_IS_BOOLEAN with an actual boolean-configured feature
key from the codebase so the runtime contract is fully exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/unit/build-features.test.ts`:
- Around line 45-90: Add unit tests for parity between devtools and logs by
creating a new describe block for isFeatureLogsEnabled that mirrors the existing
isFeatureDevtoolsEnabled cases: 1) assert true when the feature is
boolean-enabled, 2) assert false when boolean-disabled, 3) read logs flag from
object config (e.g., webVitals: { logs: false } and thirdPartyScripts: { logs:
true }), and 4) default logs to true when not specified in an object (e.g.,
webVitals: { devtools: false, logs: undefined } or only other flags present).
Use the same test helper makeOptions and call isFeatureLogsEnabled(opts,
'<featureName>') in each test to match the existing test matrix for
isFeatureDevtoolsEnabled.

In `@test/unit/core/features.test.ts`:
- Around line 91-109: Add a test case to cover the boolean-feature path by
calling getFeatureOptions with a feature key that is configured as a boolean and
asserting it returns undefined; specifically, inside the
describe('getFeatureOptions') block add an it('returns undefined for a boolean
feature') that calls getFeatureOptions('FEATURE_KEY_THAT_IS_BOOLEAN') and
expects .toBeUndefined(), replacing FEATURE_KEY_THAT_IS_BOOLEAN with an actual
boolean-configured feature key from the codebase so the runtime contract is
fully exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a662f9d9-99c7-4d1f-a47d-3f0af171fade

📥 Commits

Reviewing files that changed from the base of the PR and between 4a9ec03 and 7fffd5f.

📒 Files selected for processing (2)
  • test/unit/build-features.test.ts
  • test/unit/core/features.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant