Skip to content

refactor: overhaul stylesheet pipeline and improve Tailwind HMR across Analog#2226

Merged
brandonroberts merged 33 commits intoanalogjs:alphafrom
benpsnyder:fix/tailwind
Apr 5, 2026
Merged

refactor: overhaul stylesheet pipeline and improve Tailwind HMR across Analog#2226
brandonroberts merged 33 commits intoanalogjs:alphafrom
benpsnyder:fix/tailwind

Conversation

@benpsnyder
Copy link
Copy Markdown
Contributor

@benpsnyder benpsnyder commented Apr 3, 2026

PR Checklist

Related:

Affected scope

  • Primary scope: vite-plugin-angular
  • Secondary scopes: platform, router, vite-plugin-nitro, content, create-analog, docs, repo tooling, examples, test apps

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

This branch preserves intentional commit boundaries across multiple packages and follow-up review fixes. A rebase merge keeps that history readable without adding an extra merge commit.

What is the new behavior?

This branch is broader than the PR title alone suggests.

The center of gravity is still Angular component stylesheet HMR in @analogjs/vite-plugin-angular, but the branch also includes the dedicated reproduction app, route/content/runtime fixes needed to validate that work in-repo, the Tailwind full-reload repro alignment, and the follow-up maintainer review fixes that landed on top.

Highlights:

  • @analogjs/vite-plugin-angular centralizes stylesheet handling for HMR, expands debug logging controls, improves component stylesheet HMR, restores expected optimizeDeps prebundling, cleans up stale metadata on file deletion, removes duplicate Vite-ignore injection, fixes class-binding conflict false positives, and limits component metadata extraction to actual @Component(...) decorators.
  • The dedicated tailwind-debug-app and its e2e coverage now reproduce the current upstream Tailwind full-reload behavior from tailwindlabs/tailwindcss#19903 instead of asserting the opposite behavior, while the supporting debug stream and diagnostics flow were tightened based on review feedback.
  • @analogjs/platform adds dev-time route idiom diagnostics, clearer debug reload reasons, cached page-route discovery for diagnostics, and full rescans when route graph shape changes.
  • @analogjs/router keeps explicit app-route precedence while preserving historical last-wins behavior for equal-priority collisions, and route metadata listeners are now active during app init instead of being deferred past first navigation.
  • @analogjs/vite-plugin-nitro no longer requires an SSR entry for ssr: false apps that explicitly opt out of prerender routes, which unblocks production builds for the repro app.
  • @analogjs/content keeps the devtools API on the dedicated @analogjs/content/devtools entrypoint instead of the main package entrypoint.
  • create-analog scaffolds the Tailwind v4 PostCSS config needed by the debug workflow.
  • Repo dependency/catalog cleanup and docs/test updates are included as supporting work.

Test plan

This PR description reflects targeted validation that was actually run on the branch; it does not claim a fresh full-workspace validation pass after the latest commit stack rewrite.

  • nx format:check
  • pnpm build
  • pnpm test
  • Targeted verification

Targeted verification run on this branch:

  • pnpm vitest run packages/vite-plugin-angular/src/lib/component-resolvers.spec.ts packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts packages/platform/src/lib/router-plugin.spec.ts packages/platform/src/lib/utils/debug.spec.ts
  • pnpm nx test tailwind-debug-app --runInBand
  • pnpm vitest run --config packages/vite-plugin-angular/vite.config.ts packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • pnpm vitest run --config packages/vite-plugin-angular/vite.config.ts packages/vite-plugin-angular/src/lib/angular-vite-plugin-live-reload.spec.ts
  • CI=1 pnpm nx run tailwind-debug-app-e2e:e2e
  • pnpm nx run tailwind-debug-app:build:production
  • pnpm nx test vite-plugin-nitro --runInBand
  • pnpm oxlint -c oxlint.config.ts --quiet apps/tailwind-debug-app/src/app/debug/debug-stream.service.ts apps/tailwind-debug-app/src/app/debug/hmr-diagnostics.ts apps/tailwind-debug-app/src/app/probes/debug-stream-panel.component.ts apps/tailwind-debug-app/src/app/probes/style-probe.component.spec.ts apps/tailwind-debug-app/vite.config.ts apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts packages/platform/src/lib/content-plugin.ts packages/platform/src/lib/router-plugin.ts packages/platform/src/lib/router-plugin.spec.ts packages/platform/src/lib/utils/debug.spec.ts packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts packages/vite-plugin-angular/src/lib/component-resolvers.ts packages/vite-plugin-angular/src/lib/component-resolvers.spec.ts packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.spec.ts

Does this PR introduce a breaking change?

  • Yes
  • No

Breaking change:

  • @analogjs/content devtools exports are intentionally no longer re-exported from the main @analogjs/content entrypoint. Consumers must import them from @analogjs/content/devtools.

Other information

Current branch stack relative to alpha (33 commits):

Commit Scope Summary
6ce1d364 vite-plugin-angular centralize stylesheet handling for hmr
eba6e08e vite-plugin-angular expand debug logging controls
2c49f0a2 content split devtools into a dedicated entrypoint
8cbb8fd4 router prefer app routes over shared duplicates
f23ce5a2 platform restore analog-app action and content examples
8eec739f router cover legacy PageServerAction compatibility
84002ee3 create-analog scaffold Tailwind v4 PostCSS config
b9b60be7 router preserve app route precedence in production
09482a9e platform add dev-time route idiom diagnostics
d8742e39 vite-plugin-angular improve component stylesheet hmr
87ad7d21 test align fixtures with explicit selector enforcement
2f2f7171 router stabilize legacy page action fixture
162043de platform emit debug reload reasons for content changes
605d2150 build adopt workspace catalogs for repo deps
5156354a router defer route metadata setup during bootstrap
7c9c2e9e chore add tailwind debug coverage and stabilize e2e
677a4a69 vite-plugin-angular handle > inside quoted class bindings
1683e4ee vite-plugin-angular ignore [class.foo] in ngClass conflict guard
6a6efc4a router preserve last-wins behavior for equal-priority collisions
7eafecb9 router register route metadata listeners during app init
b55a4366 vite-plugin-angular restore rxjs optimizeDeps prebundling
8a93d0cd vite-plugin-angular evict stale metadata on file deletion
002de6c9 platform cache page-route discovery for diagnostics
61807f34 vite-plugin-angular dedupe vite-ignore hmr injection
ba044b0f chore remove investigation snapshot artifacts
135c883e build move all root package deps to workspace catalogs
fb701b8f docs note Angular HMR version requirements
9c6a2966 fix harden tailwind debug app HMR diagnostics
779c9c8f platform refresh route diagnostics on route graph changes
c37ff35f vite-plugin-angular restrict metadata extraction to component decorators
bcadc13e vite-plugin-angular tighten resolver coverage docs
9e0be352 vite-plugin-angular align tailwind debug app e2e with upstream full-reload repro
c091cf7a vite-plugin-nitro avoid SSR entry lookup for ssr: false apps with empty prerender routes

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 3, 2026

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit c091cf7
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/69d1e6d94b49f50008a8e486
😎 Deploy Preview https://deploy-preview-2226--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 3, 2026

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit c091cf7
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/69d1e6d9ec1747000807eaac
😎 Deploy Preview https://deploy-preview-2226--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds Angular HMR (hmr) as the preferred option (keeps liveReload as a compatibility alias), extends Tailwind v4 support with PostCSS (postcss + @tailwindcss/postcss), and updates generators/tests/docs to scaffold PostCSS and Tailwind changes. It extracts content devtools into a dedicated entrypoint, adds a stylesheet registry and preprocessing pipeline (AnalogStylesheetRegistry) for externalized component styles, makes route filename ordering deterministic with collision-priority rules, introduces route-idiom diagnostics, expands debug websocket notifications, and adds a tailwind-debug app with HMR wiretap, E2E tests, and many related tests/config/lint updates.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

@github-actions github-actions bot added scope:platform Changes in @analogjs/platform scope:repo Repository metadata and tooling scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vite-plugin-nitro Changes in @analogjs/vite-plugin-nitro labels Apr 3, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

This PR touches multiple package scopes: astro-angular, content, create-analog, nx-plugin, platform, router, storybook-angular, vite-plugin-angular, vite-plugin-nitro, vitest-angular.

Please confirm the changes are closely related. Squash merge is highly preferred. If you recommend a non-squash merge, add a brief note explaining why the commit boundaries matter and why this PR should bypass focused changes per package.

@github-actions github-actions bot added the scope:router Changes in @analogjs/router label Apr 4, 2026
@github-actions github-actions bot added scope:create-analog Changes in create-analog scope:nx-plugin Changes in @analogjs/nx-plugin scope:storybook-angular Changes in @analogjs/storybook-angular scope:router Changes in @analogjs/router and removed scope:router Changes in @analogjs/router labels Apr 4, 2026
@benpsnyder benpsnyder changed the title fix: tailwind v4 on all codepaths fix(vite-plugin-angular): stabilize Tailwind v4 stylesheet pipeline Apr 4, 2026
@benpsnyder benpsnyder marked this pull request as ready for review April 4, 2026 16:48
Copy link
Copy Markdown

@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: 5

🧹 Nitpick comments (5)
packages/router/src/lib/routes.spec.ts (1)

978-1012: Spy cleanup should use afterEach for robustness.

If an assertion fails before spy.mockRestore(), the spy won't be cleaned up, potentially affecting subsequent tests. Other describe blocks in this file (lines 874-876, 1058-1060) use afterEach for spy restoration.

♻️ Suggested pattern for consistent cleanup
   describe('duplicate route precedence', () => {
     class AppRouteComponent {}
     class SharedRouteComponent {}
+
+    afterEach(() => {
+      vi.restoreAllMocks();
+    });
 
     it('prefers app-local page routes over additional/shared page routes', async () => {
       const spy = vi.spyOn(console, 'warn').mockImplementation(() => {
         /* noop */
       });
       // ... test body ...
-
-      spy.mockRestore();
     });
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/lib/routes.spec.ts` around lines 978 - 1012, The test
creates a console.warn spy via vi.spyOn inside the it block and calls
spy.mockRestore() at the end, which risks leaking the spy if the assertion
fails; refactor the test to create the spy in a beforeEach (or at top of the
describe) and restore it in an afterEach so cleanup always runs; update the
'duplicate route precedence' describe to set up the spy for console.warn (used
in the test that calls createBaseRoutes and inspects route.loadChildren) in
beforeEach and call spy.mockRestore() in afterEach to ensure robust teardown.
apps/analog-app/vite.config.ts (1)

78-78: Consider migrating from liveReload to hmr for consistency.

This PR introduces hmr as the primary option with liveReload retained as a deprecated compatibility alias. The example app should demonstrate the preferred approach.

♻️ Suggested change
-        liveReload: true,
+        hmr: true,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/analog-app/vite.config.ts` at line 78, Replace the dev-server option
usage of liveReload with the preferred hmr option: add an hmr entry (e.g. hmr:
true or an hmr config object) and keep liveReload present only as a deprecated
compatibility alias (leave liveReload: true but mark/comment it deprecated or
move it alongside hmr). Update the dev server config where liveReload is
currently set (the liveReload property in vite.config.ts) to use hmr as the
primary key and retain liveReload solely for backward compatibility.
packages/vite-plugin-angular/src/lib/utils/debug.ts (1)

4-7: Unused imports: DEBUG_LOG_FILENAME and wrapInstancesForFileLog

These symbols are imported but not referenced anywhere in this module. The file-logging wrappers are applied inside debug-harness.ts via installFileWrappers, which receives the instanceGroups from the harness config—this file only passes the instances, it doesn't call the wrappers directly.

If these were added for future use, consider deferring the import until needed to keep the module surface clean.

🧹 Remove unused imports
-import {
-  DEBUG_LOG_FILENAME,
-  wrapInstancesForFileLog,
-} from './debug-log-file.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vite-plugin-angular/src/lib/utils/debug.ts` around lines 4 - 7,
Remove the unused imports DEBUG_LOG_FILENAME and wrapInstancesForFileLog from
the import statement in debug.ts since this module doesn't reference them; if
you intended to use them later, instead perform a dynamic import (e.g.,
import('./debug-log-file.js') inside the function that needs them) so the module
surface stays clean and avoids importing file-logging helpers that are applied
elsewhere (installFileWrappers in debug-harness).
packages/vite-plugin-angular/src/lib/stylesheet-registry.ts (2)

110-121: Basename alias can cause lookup collisions, but impact is limited to dev-time HMR.

When multiple components have stylesheets with the same basename (e.g., libs/feature-a/card.component.css and libs/feature-b/card.component.css), the later registration overwrites the basename alias. This means a basename-only lookup would serve the most recent content.

This is acceptable because:

  1. The publicId (sha256 hash) remains unique and is the primary lookup key
  2. Production builds inline styles and don't use this registry
  3. In HMR scenarios, the most recent edit is typically what we want

If strict correctness becomes necessary, the basename alias could be scoped or omitted.

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

In `@packages/vite-plugin-angular/src/lib/stylesheet-registry.ts` around lines 110
- 121, The current registry pushes basename(resourceFile) into the aliases array
which can cause lookup collisions when different components share the same
stylesheet basename; update the registration in stylesheet-registry.ts (the
aliases array construction around resourceFile/normalizedResourceFile and
basename) to avoid collisions by either removing the basename alias entirely or
scoping it (e.g., prepend a component-specific prefix or part of the normalized
path) so that basename no longer overwrites unrelated entries during HMR; ensure
the chosen change still includes resourceFile and normalizedResourceFile entries
and document the decision in a comment near the aliases construction.

70-82: rewriteRelativeCssImports handles common cases, but url() syntax is unhandled.

The regex /@import\s+(['"])(\.[^'"]+)\1/g correctly rewrites string-based relative imports but doesn't handle the @import url(./foo.css) syntax. This is likely acceptable since:

  1. Angular component stylesheets rarely use url() for imports
  2. The primary use case is Tailwind's @reference which uses string syntax

If url() support becomes necessary, the regex could be extended:

🔧 Optional: Extend to handle url() syntax
 export function rewriteRelativeCssImports(
   code: string,
   filename: string,
 ): string {
   const cssDir = dirname(filename);
-  return code.replace(
+  // Handle string-based `@import`
+  let result = code.replace(
     /@import\s+(['"])(\.[^'"]+)\1/g,
     (_match, quote, relPath) => {
       const absPath = resolve(cssDir, relPath);
       return `@import ${quote}${absPath}${quote}`;
     },
   );
+  // Handle url() based `@import`
+  result = result.replace(
+    /@import\s+url\((['"]?)(\.[^'")\s]+)\1\)/g,
+    (_match, quote, relPath) => {
+      const absPath = resolve(cssDir, relPath);
+      return `@import url(${quote}${absPath}${quote})`;
+    },
+  );
+  return result;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vite-plugin-angular/src/lib/stylesheet-registry.ts` around lines 70
- 82, rewriteRelativeCssImports only rewrites string-form `@import` ('./x.css')
but misses `@import` url(./x.css) and `@import` url('./x.css') forms; update the
regex in rewriteRelativeCssImports to also match url(...) variants (both quoted
and unquoted) and in the replace callback detect whether the match used url(...)
or a plain string so you can resolve the relative relPath with resolve(cssDir,
relPath) and recompose the import preserving the original wrapper (either
`@import` "..." / `@import` '...' or `@import` url(...) with its quotes/unquoted
content) to avoid breaking existing import syntax.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/router/src/lib/routes.spec.ts`:
- Around line 994-1003: The test incorrectly looks up a nested route by matching
'blog/:slug' on the top-level routes array; update the test to traverse the
nested route tree returned by createBaseRoutes to find the child route (first
find the top-level route with path 'blog', then access its children and find the
child with path ':slug' and call its loadChildren) so route is defined and
loadedRoutes can be asserted against AppRouteComponent (update references in
this spec around the routes variable and route.loadChildren usage).

In `@packages/vite-plugin-angular/src/lib/angular-jit-plugin.ts`:
- Around line 48-51: The catch block that currently only calls debugStyles('jit
css compilation error', { styleIdHash, error: String(e) }) must also emit a
visible dev warning/error (e.g., console.warn or the plugin/vite logger) so JIT
preprocessing failures are not silent; update the error path in
angular-jit-plugin.ts to both call debugStyles for structured details and also
emit a human-readable warning including styleIdHash and the error message so
users see the failure in normal dev output, while still returning the existing
empty exported stylesheet fallback.

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts`:
- Around line 28-33: The current test "accepts liveReload as a compatibility
alias" calls angular({ liveReload: true }) but only asserts for a plugin that is
always present; change it to assert that HMR-specific plugins returned by
angularVitestPlugins() (i.e., the plugins that should be included when HMR is
enabled) are present when calling angular({ liveReload: true }) and are absent
when calling angular({ liveReload: false }) or default, by comparing the plugin
names from plugins.map(p => p.name) against the names produced by
angularVitestPlugins() (or specific HMR plugin name strings) to verify
liveReload actually toggles HMR inclusion.

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`:
- Around line 558-574: When Angular version is detected as angularFullVersion <
190000 we silently disable HMR by setting pluginOptions.hmr = false; add a
user-facing warning there so users know their hmr config was overridden. Update
the branch that checks angularFullVersion to call a visible logger (e.g.,
console.warn or the plugin's logger) with a clear message that Angular <19 does
not support externalRuntimeStyles/_enableHmr and HMR is disabled, and mirror the
debugHmr context (angularVersion and isTest where relevant) so debugging info is
available; keep the existing isTest block and debugHmr call unchanged.

In `@packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.ts`:
- Line 35: The import debugPrerender is unused; either remove it from the import
list in vite-plugin-nitro (alongside debugNitro and debugSsr) or add explicit
prerender debug calls where prerender route handling happens (the prerender
route handling blocks) using debugPrerender(...) to log prerender-specific
events; update references in the file so only used debug instances are imported
and ensure any added debugPrerender calls mirror the style/arguments of existing
debugNitro/debugSsr invocations.

---

Nitpick comments:
In `@apps/analog-app/vite.config.ts`:
- Line 78: Replace the dev-server option usage of liveReload with the preferred
hmr option: add an hmr entry (e.g. hmr: true or an hmr config object) and keep
liveReload present only as a deprecated compatibility alias (leave liveReload:
true but mark/comment it deprecated or move it alongside hmr). Update the dev
server config where liveReload is currently set (the liveReload property in
vite.config.ts) to use hmr as the primary key and retain liveReload solely for
backward compatibility.

In `@packages/router/src/lib/routes.spec.ts`:
- Around line 978-1012: The test creates a console.warn spy via vi.spyOn inside
the it block and calls spy.mockRestore() at the end, which risks leaking the spy
if the assertion fails; refactor the test to create the spy in a beforeEach (or
at top of the describe) and restore it in an afterEach so cleanup always runs;
update the 'duplicate route precedence' describe to set up the spy for
console.warn (used in the test that calls createBaseRoutes and inspects
route.loadChildren) in beforeEach and call spy.mockRestore() in afterEach to
ensure robust teardown.

In `@packages/vite-plugin-angular/src/lib/stylesheet-registry.ts`:
- Around line 110-121: The current registry pushes basename(resourceFile) into
the aliases array which can cause lookup collisions when different components
share the same stylesheet basename; update the registration in
stylesheet-registry.ts (the aliases array construction around
resourceFile/normalizedResourceFile and basename) to avoid collisions by either
removing the basename alias entirely or scoping it (e.g., prepend a
component-specific prefix or part of the normalized path) so that basename no
longer overwrites unrelated entries during HMR; ensure the chosen change still
includes resourceFile and normalizedResourceFile entries and document the
decision in a comment near the aliases construction.
- Around line 70-82: rewriteRelativeCssImports only rewrites string-form `@import`
('./x.css') but misses `@import` url(./x.css) and `@import` url('./x.css') forms;
update the regex in rewriteRelativeCssImports to also match url(...) variants
(both quoted and unquoted) and in the replace callback detect whether the match
used url(...) or a plain string so you can resolve the relative relPath with
resolve(cssDir, relPath) and recompose the import preserving the original
wrapper (either `@import` "..." / `@import` '...' or `@import` url(...) with its
quotes/unquoted content) to avoid breaking existing import syntax.

In `@packages/vite-plugin-angular/src/lib/utils/debug.ts`:
- Around line 4-7: Remove the unused imports DEBUG_LOG_FILENAME and
wrapInstancesForFileLog from the import statement in debug.ts since this module
doesn't reference them; if you intended to use them later, instead perform a
dynamic import (e.g., import('./debug-log-file.js') inside the function that
needs them) so the module surface stays clean and avoids importing file-logging
helpers that are applied elsewhere (installFileWrappers in debug-harness).
🪄 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: 7c843a0a-ace2-4a12-896a-d376f1f711e9

📥 Commits

Reviewing files that changed from the base of the PR and between 63a4eed and 8b98dc7.

⛔ Files ignored due to path filters (3)
  • .gitignore is excluded by none and included by none
  • Plan.md is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (35)
  • apps/analog-app/package.json
  • apps/analog-app/src/app/app.config.ts
  • apps/analog-app/vite.config.ts
  • apps/blog-app/src/app/app.config.ts
  • package.json
  • packages/create-analog/__tests__/cli.spec.ts
  • packages/create-analog/index.js
  • packages/nx-plugin/src/generators/app/generator.spec.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-config.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts
  • packages/nx-plugin/src/generators/app/versions/nx_18_X/versions.ts
  • packages/nx-plugin/src/generators/app/versions/tailwind-dependencies.ts
  • packages/platform/src/lib/options.ts
  • packages/platform/src/lib/platform-plugin.ts
  • packages/platform/src/lib/utils/debug.spec.ts
  • packages/router/src/lib/route-builder.ts
  • packages/router/src/lib/routes.spec.ts
  • packages/storybook-angular/README.md
  • packages/storybook-angular/src/lib/preset.spec.ts
  • packages/storybook-angular/src/lib/preset.ts
  • packages/storybook-angular/src/types.ts
  • packages/vite-plugin-angular/src/lib/angular-jit-plugin.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin-live-reload.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
  • packages/vite-plugin-angular/src/lib/host.spec.ts
  • packages/vite-plugin-angular/src/lib/host.ts
  • packages/vite-plugin-angular/src/lib/plugins/file-replacements.plugin.ts
  • packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts
  • packages/vite-plugin-angular/src/lib/stylesheet-registry.ts
  • packages/vite-plugin-angular/src/lib/utils/debug.spec.ts
  • packages/vite-plugin-angular/src/lib/utils/debug.ts
  • packages/vite-plugin-nitro/src/index.ts
  • packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.ts
  • tools/scripts/debug-nx-project-graph.mts

@github-actions github-actions bot added scope:content Changes in @analogjs/content scope:docs Documentation changes labels Apr 4, 2026
Copy link
Copy Markdown

@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: 3

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

Inline comments:
In `@apps/analog-app-e2e/tests/legacy-server-action.spec.ts`:
- Line 6: The test is waiting on a non-existent attribute selector (page.locator
with selector "form[data-state]") which causes beforeEach timeouts; update the
readiness check in the test to wait for a stable existing element instead—either
change the locator to target the plain "form" element (using
page.locator("form").waitFor) or add and target a stable attribute (e.g.,
data-testid on the form) that you also add to the legacy-action page component;
update the test's beforeEach locator reference (page.locator and the selector
"form[data-state]") to use the chosen stable selector.

In `@apps/analog-app/src/app/pages/legacy-action.page.ts`:
- Around line 27-28: The label's for attribute ("email") isn't linked to the
input because the input lacks id="email"; update the input element in
legacy-action.page.ts (the <input type="email" name="email" />) to include
id="email" (or change the label's for to match the existing id if you prefer a
different identifier) so the <label for="email"> correctly associates with the
input control for accessibility and form behavior.

In `@apps/docs-app/docs/integrations/angular-material/index.md`:
- Around line 167-170: The manual install command only adds `@tailwindcss/vite`
but the note explains using Tailwind v4 with a PostCSS pipeline; update the
install step where the docs add "@tailwindcss/vite" to instead install the full
set of dev dependencies required for the described flow: tailwindcss,
`@tailwindcss/vite`, postcss, and `@tailwindcss/postcss` (use -D/dev flag). Locate
the npm install line in the Angular Material integration docs and replace the
single-package install with the multi-package dev-install so the described setup
matches the actual packages required.
🪄 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: 02f76b7d-eaa5-4233-9b4b-103f899e7565

📥 Commits

Reviewing files that changed from the base of the PR and between 8b98dc7 and 4d8844b.

📒 Files selected for processing (17)
  • apps/analog-app-e2e/tests/legacy-server-action.spec.ts
  • apps/analog-app/src/app/pages/client/(client).page.ts
  • apps/analog-app/src/app/pages/legacy-action.page.ts
  • apps/analog-app/src/app/pages/legacy-action.server.ts
  • apps/analog-app/src/app/pages/newsletter.page.ts
  • apps/analog-app/src/app/pages/newsletter.server.ts
  • apps/analog-app/src/routeTree.gen.ts
  • apps/docs-app/docs/guides/migrating.md
  • apps/docs-app/docs/integrations/angular-material/index.md
  • apps/docs-app/docs/integrations/storybook/index.md
  • apps/docs-app/docs/packages/create-analog/overview.md
  • apps/docs-app/docs/packages/vite-plugin-angular/css-preprocessors.md
  • packages/content/devtools/src/index.ts
  • packages/content/package.json
  • packages/content/src/index.spec.ts
  • packages/content/src/index.ts
  • packages/content/vite.config.lib.ts
💤 Files with no reviewable changes (2)
  • apps/analog-app/src/app/pages/newsletter.page.ts
  • packages/content/src/index.ts
✅ Files skipped from review due to trivial changes (6)
  • apps/docs-app/docs/integrations/storybook/index.md
  • packages/content/src/index.spec.ts
  • packages/content/devtools/src/index.ts
  • apps/docs-app/docs/packages/create-analog/overview.md
  • apps/docs-app/docs/packages/vite-plugin-angular/css-preprocessors.md
  • apps/analog-app/src/routeTree.gen.ts

@benpsnyder
Copy link
Copy Markdown
Contributor Author

Maintainer context from apps/analog-app while validating this branch:

What We Fixed And Why

This app exposed three separate regressions while exercising production build output through analog-app-e2e.

1. The browser bundle pulled in Vite-only content devtools code

What broke:

The @analogjs/content main entrypoint re-exported devtools symbols. Those devtools modules depend on vite, which is a build-time dependency and not safe to ship into the browser bundle.

In workspace development, that meant the app client bundle could resolve through source, pull Vite internals into runtime code, and fail during hydration with errors like createRequire is not a function.

What we changed:

  • Removed devtools exports from the main @analogjs/content entrypoint.
  • Added a dedicated @analogjs/content/devtools secondary entrypoint.
  • Added the matching library build output and package export so the devtools API is still available intentionally.

Why this is the right fix:

The main content package should stay browser-safe. Devtools are optional and build-time oriented, so they need an explicit opt-in entrypoint instead of piggybacking on the default one.

2. The newsletter page used the old action path

What broke:

The newsletter example still used the older server action shape and had an unsupported template binding. That left the example out of step with the current defineAction(...) flow and caused the newsletter e2e to fail.

What we changed:

  • Converted the newsletter server handler to defineAction(...).
  • Added schema validation for the email field.
  • Removed the unsupported (onStateChange) binding from the template.

Why this is the right fix:

This example app is part of the regression surface for Analog features. It should demonstrate the current action API, not a legacy path that quietly drifts out of coverage.

3. The no-SSR /client route depended on server-only rendering

What broke:

The /client route is intentionally client-only, but it imported and rendered a server-only component. In the production app bootstrap path that led to a runtime failure on navigation, the page never rendered, and the JSON-LD test failed because the route content never mounted.

What we changed:

  • Simplified the /client page so it is fully client-safe.
  • Kept the page title and JSON-LD metadata.
  • Kept a small interactive signal example so the page still demonstrates client rendering.

Why this is the right fix:

A no-SSR route has to be self-consistent. If the route is meant to render only on the client, its example code cannot depend on server-only components and still serve as a reliable regression target.

Regression coverage

  • analog-app-e2e covers the client-only /client route and the newsletter flow.
  • analog-app-e2e/tests/legacy-server-action.spec.ts keeps the supported PageServerAction flow covered at the app level because that compatibility path is still intentionally present in the example app.
  • packages/content/src/index.spec.ts guards the public entrypoint split so devtools exports do not accidentally leak back into the main browser-safe entrypoint.
  • packages/router/server/actions/src/page-server-action-compat.spec.ts keeps the same PageServerAction compatibility pattern covered at the package level until that API is explicitly deprecated and removed.

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/vite-plugin-angular/src/lib/component-resolvers.ts (1)

57-101: ⚠️ Potential issue | 🟡 Minor

Limit inline template extraction to actual @Component() decorators.

collectComponentUrls() still walks every Property node in the file, so the new inlineTemplates path will also pick up unrelated objects like const preview = { template: '<div />' }. That makes getInlineTemplates() disagree with getAngularComponentMetadata() on the same source and returns false positives for callers expecting component-only metadata.

💡 Minimal fix
 /** Extract inline `template` strings from Angular component source. */
 export function getInlineTemplates(code: string): string[] {
-  return collectComponentUrls(code).inlineTemplates;
+  return getAngularComponentMetadata(code).flatMap(
+    ({ inlineTemplates }) => inlineTemplates,
+  );
 }

Please add a regression case with a non-@Component { template: '...' } object in the same file.

Also applies to: 208-210

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

In `@packages/vite-plugin-angular/src/lib/component-resolvers.ts` around lines 57
- 101, collectComponentUrls is collecting any Property named "template" in the
file, causing inlineTemplates to include non-component objects; restrict
extraction to only Properties that belong to an ObjectExpression passed as the
first argument to a `@Component`(...) decorator. In the Visitor.Property callback
(inside collectComponentUrls) check the node's parent chain: ensure the
Property's parent is an ObjectExpression which is the first argument of a
CallExpression whose callee is Identifier 'Component' (i.e., decorator call),
before pushing to inlineTemplates (and similarly for templateUrl/styleUrl
handling if needed). Add a regression test that includes a non-@Component const
like const preview = { template: '<div/>' } and assert getInlineTemplates() does
not include that template while getAngularComponentMetadata()/component
extraction still finds real `@Component` templates.
♻️ Duplicate comments (2)
package.json (1)

71-71: ⚠️ Potential issue | 🟡 Minor

Keep Angular runtime package version strategy consistent.

Line 71 uses ~21.2.6 while the rest of the Angular runtime set is exact-pinned (21.2.6). That can introduce patch drift for just @angular/platform-server across environments. If intentional, please document why this package is exempt.

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json
pkg = json.load(open('package.json'))
deps = pkg.get('dependencies', {})
print("Angular dependency versions:")
for k in sorted([k for k in deps if k.startswith("@angular/")]):
    print(f"  {k}: {deps[k]}")
PY

Expected result: confirm whether mixed exact/tilde pinning is intentional and acceptable for CI/release reproducibility.

As per coding guidelines for package.json: focus on workspace-wide tooling impact, dependency changes, and CI/release implications.

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

In `@package.json` at line 71, The dependency entry for "@angular/platform-server"
is using tilde pinning ("~21.2.6") while the rest of the Angular runtime deps
are exact-pinned ("21.2.6"), causing inconsistent version strategy; update the
"@angular/platform-server" entry in package.json to use the exact version
"21.2.6" to match the other `@angular/`* runtime packages (or, if tilde pinning is
intentional, add a short documented justification in the repo docs and a CI note
referencing "@angular/platform-server" to explain the exemption).
packages/router/src/lib/provide-file-router-base.ts (1)

122-139: ⚠️ Potential issue | 🟠 Major

Behavioral regression: meta tags and JSON-LD disabled in DEV mode

The import.meta.env.DEV guard at lines 125-127 returns a no-op, completely disabling updateMetaTagsOnRouteChange and updateJsonLdOnRouteChange during development. This means developers won't see their JSON-LD or meta tags while working locally—a significant DX regression for anyone iterating on SEO or structured data.

Additionally, a past reviewer flagged that deferring via queueMicrotask after APP_BOOTSTRAP_LISTENER could cause meta tags/JSON-LD to be absent during the first SSR render if the server resolves HTML before the microtask fires. The retrieved learning confirms there's already a pre-existing JSON-LD SSR failure on alpha, but this timing change could exacerbate or mask related issues.

Consider:

  1. Removing the DEV guard or making it configurable so developers can validate structured data locally.
  2. Validating SSR prerendering behavior to ensure the subscription is active before the initial NavigationEnd.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/router/src/lib/provide-file-router-base.ts` around lines 122 - 139,
The DEV-only early return in the APP_BOOTSTRAP_LISTENER factory disables
updateMetaTagsOnRouteChange and updateJsonLdOnRouteChange during development;
remove that import.meta.env.DEV guard (or replace it with a configurable flag)
so the factory always wires the router subscriptions, and stop deferring
subscription setup via queueMicrotask—create the Router/Meta/DOCUMENT injections
and call updateMetaTagsOnRouteChange and updateJsonLdOnRouteChange synchronously
(or schedule them after setting up subscriptions but not via queueMicrotask) so
the subscription is active before the first NavigationEnd; ensure changes are
applied in the factory that returns the bootstrap callback (referencing provide:
APP_BOOTSTRAP_LISTENER, useFactory, updateMetaTagsOnRouteChange,
updateJsonLdOnRouteChange, queueMicrotask, inject(Router), inject(Meta),
inject(DOCUMENT)).
🧹 Nitpick comments (4)
packages/platform/src/lib/content-plugin.ts (1)

194-198: Use normalizedPath in the debug payload for deterministic diagnostics.

You already normalize path for directory checks, but the debug event emits the raw value. Emitting normalizedPath avoids platform-specific separator drift in debug tooling/e2e logs.

Proposed patch
             server.ws.send('analog:debug-full-reload', {
               plugin: 'platform:content-plugin',
               reason: 'content-file-set-changed',
-              path,
+              path: normalizedPath,
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/platform/src/lib/content-plugin.ts` around lines 194 - 198, The
debug event currently emits the raw path variable which can vary by OS; change
the payload to send the already-computed normalizedPath instead. Locate the
server.ws.send call (server.ws.send('analog:debug-full-reload', {...})) in the
content-plugin code and replace the path property with path: normalizedPath (or
rename to normalizedPath in the payload if you prefer) so the debug payload
consistently uses the normalizedPath value.
packages/platform/src/lib/utils/debug.spec.ts (1)

116-120: Consider adding explicit assertion on the first argument for clarity.

The test accesses callArgs[1] to verify workspaceRoot forwarding. Adding a brief assertion on callArgs[0] (the debug instances array) would make the test more self-documenting and catch accidental argument reordering.

Optional: strengthen the assertion
   it('uses provided workspaceRoot for file path', () => {
     applyDebugOption({ logFile: true }, '/custom/root');
     const callArgs = vi.mocked(wrapInstancesForFileLog).mock.calls[0];
+    expect(Array.isArray(callArgs[0])).toBe(true); // instances array
     expect(callArgs[1]).toContain('/custom/root');
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/platform/src/lib/utils/debug.spec.ts` around lines 116 - 120, Add an
explicit assertion that the first argument passed to wrapInstancesForFileLog is
the debug instances array to prevent silent argument reordering: after calling
applyDebugOption({ logFile: true }, '/custom/root') and grabbing callArgs from
vi.mocked(wrapInstancesForFileLog).mock.calls[0], assert something about
callArgs[0] (e.g., that it's an array and contains the expected debug instance
shape or length) before asserting callArgs[1] contains '/custom/root'; reference
wrapInstancesForFileLog and applyDebugOption to locate where to add this check
in the test.
packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts (1)

24-33: Consider adding test coverage for @import url(...) form.

The rewriteRelativeCssImports function handles both @import "./..." and @import url("./...") forms, but only the string form is tested here. Given that url()-form rewriting is specifically called out as expected behavior for Tailwind v4 compatibility, a test for that path would strengthen regression coverage.

🧪 Suggested additional test case
   it('rewrites relative css imports to absolute paths', () => {
     expect(
       rewriteRelativeCssImports(
         '@import "./submenu/submenu.component.css";\n.demo { color: red; }',
         '/project/src/app/header.component.css',
       ),
     ).toBe(
       '@import "/project/src/app/submenu/submenu.component.css";\n.demo { color: red; }',
     );
   });
+
+  it('rewrites relative css imports in url() form to absolute paths', () => {
+    expect(
+      rewriteRelativeCssImports(
+        '@import url("./submenu/submenu.component.css");\n.demo { color: red; }',
+        '/project/src/app/header.component.css',
+      ),
+    ).toBe(
+      '@import url("/project/src/app/submenu/submenu.component.css");\n.demo { color: red; }',
+    );
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts` around
lines 24 - 33, Add a new unit test in stylesheet-registry.spec.ts that exercises
rewriteRelativeCssImports with the `@import` url(...) form: call
rewriteRelativeCssImports with a CSS string containing `@import`
url("./submenu/submenu.component.css"); and the same base path used in the
existing test (e.g. '/project/src/app/header.component.css'), and assert the
returned CSS rewrites the relative url to the absolute path
'/project/src/app/submenu/submenu.component.css' while leaving the rest of the
CSS intact; ensure the test name clearly states it covers the url(...) import
form.
apps/tailwind-debug-app/src/app/probes/style-probe.component.spec.ts (1)

23-33: Test name slightly misleading

The test is named "preserves local state across css-only assertions" but it's actually testing the increment() method and counter display—not CSS-related behavior. Consider renaming to something like "increments and displays click count" for clarity.

That said, the test itself is lightweight and targeted per coding guidelines.

Suggested rename
-  it('preserves local state across css-only assertions', () => {
+  it('increments and displays click count', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/tailwind-debug-app/src/app/probes/style-probe.component.spec.ts` around
lines 23 - 33, Rename the test description to accurately reflect what it's
asserting: change the it(...) title in style-probe.component.spec.ts from
"preserves local state across css-only assertions" to something like "increments
and displays click count" so it clearly documents that it calls
StyleProbeComponent.increment() and checks the probe-counter text content;
update any related test comments to match the new name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs-app/docs/guides/migrating.md`:
- Line 184: Update the HMR paragraph that mentions the `hmr` option and
`liveReload` alias to explicitly state that Analog requires Angular v19 or newer
for HMR to work; add a short sentence after the existing line (the one
referencing `hmr` and `liveReload`) noting that Angular v16–v18 have HMR
forcibly disabled at runtime with a console warning and therefore HMR is
unavailable on those versions. Ensure the wording references `hmr` and
`liveReload` so readers see the compatibility note in context.

In `@apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts`:
- Around line 46-48: The test teardown currently calls
replaceProbeColor(BLUE_CLASS) which hard-resets the probe stylesheet to a
constant instead of restoring the original file; change the cleanup in
test.afterAll to restore the exact checked-in contents by using ORIGINAL_CSS (or
the variable that captured the file contents before mutation) — e.g., call
replaceProbeColor(ORIGINAL_CSS) or write the saved ORIGINAL_CSS back so the
probe stylesheet is returned byte-for-byte to its pre-test state (update
references to BLUE_CLASS -> ORIGINAL_CSS in test.afterAll and ensure
ORIGINAL_CSS is captured before any mutation).

In `@apps/tailwind-debug-app/project.json`:
- Around line 43-49: The serve-nitro target currently sets cwd to
"dist/apps/tailwind-debug-app/analog" but doesn't ensure the dist is created;
update the "serve-nitro" target so it builds before serving (e.g., add a
dependsOn: ["build"] to the serve-nitro target or change the "command" to run
the build first like "nx build tailwind-debug-app && node
--unhandled-rejections=throw ./server/index.mjs") so that the
dist/apps/tailwind-debug-app/analog directory exists before using the cwd and
running the "command".

In `@apps/tailwind-debug-app/src/app/app.component.css`:
- Line 1: Stylelint is flagging Tailwind v4's `@reference` at-rule; update your
Stylelint config to allow it by adding the rule "scss/at-rule-no-unknown" with
ignoreAtRules: ["reference"] to your .stylelintrc.json (i.e., set
scss/at-rule-no-unknown to [true, { "ignoreAtRules": ["reference"] }]) or
alternatively suppress the warning locally in the CSS file by adding the
appropriate Stylelint disable comment for the at-rule; target the
scss/at-rule-no-unknown rule and the `@reference` at-rule when making the change.

In `@apps/tailwind-debug-app/src/app/debug/debug-stream.service.ts`:
- Around line 22-27: The appendEntry helper currently just concatenates and
trims, causing duplicate events when snapshots are re-appended; change
appendEntry (and the other merge spots handling snapshots/feeds) to deduplicate
by entry.id before trimming: when merging entries (e.g., in appendEntry and the
other merge locations that add a batch/snapshot of TailwindDebugEventEntry),
build the merged list by preserving insertion order but filtering out any
entries whose id already exists (use a Set of ids), then apply the STREAM_LIMIT
slice; update the same logic in the other merge points referenced in the comment
so reconnected snapshots do not re-add entries with the same id.

In `@apps/tailwind-debug-app/src/app/debug/hmr-diagnostics.ts`:
- Around line 80-96: Wrap persistent writes in writeStoredBreadcrumbs and
writeBootCount with a try/catch to prevent storage errors (e.g.,
QuotaExceededError) from bubbling up; catch any thrown error from
storage.setItem, optionally console.warn() the failure with context (include the
key name and error), and silently ignore the error so the in-memory
breadcrumbs/HMR state continues to function. Ensure you only change the
implementations of writeStoredBreadcrumbs and writeBootCount and do not alter
their signatures.

In `@apps/tailwind-debug-app/src/app/probes/debug-stream-panel.component.ts`:
- Around line 58-63: The Clear button currently calls stream.clear() which only
wipes the live buffer; when paused() is true visibleEntries() comes from
frozenEntries(), so you must also clear the frozen view. Update the Clear
handler (the click wired to stream.clear()) to clear both the live stream and
the frozen entries returned by frozenEntries() — e.g., call stream.clear() and
also clear stream.frozenEntries() (or add/ call a
stream.clearFrozen()/resetFrozen() method if that API doesn't exist) so the UI
is empty while paused and after Clear; apply the same change to the duplicate
handler at the other location referenced.

In `@packages/platform/src/lib/router-plugin.ts`:
- Around line 137-172: The current reportRouteDiagnostics(path) only analyzes
the touched .page.ts file, so when route files are added/removed some
parent/ancestor files (e.g., products.page.ts) never get rechecked; update the
logic that invalidates discovery caches for add/unlink events to iterate
discoverRouteFiles().filter(f => f.endsWith('.page.ts')) and call
reportRouteDiagnostics for each remaining page file (or call
analyzeAnalogRouteFile for each) so all dependent pages are rescanned and
routeDiagnosticCache is updated; ensure you still skip non-.page.ts files and
preserve existing error handling in reportRouteDiagnostics and add a watcher
regression test for add/unlink flows.

---

Outside diff comments:
In `@packages/vite-plugin-angular/src/lib/component-resolvers.ts`:
- Around line 57-101: collectComponentUrls is collecting any Property named
"template" in the file, causing inlineTemplates to include non-component
objects; restrict extraction to only Properties that belong to an
ObjectExpression passed as the first argument to a `@Component`(...) decorator. In
the Visitor.Property callback (inside collectComponentUrls) check the node's
parent chain: ensure the Property's parent is an ObjectExpression which is the
first argument of a CallExpression whose callee is Identifier 'Component' (i.e.,
decorator call), before pushing to inlineTemplates (and similarly for
templateUrl/styleUrl handling if needed). Add a regression test that includes a
non-@Component const like const preview = { template: '<div/>' } and assert
getInlineTemplates() does not include that template while
getAngularComponentMetadata()/component extraction still finds real `@Component`
templates.

---

Duplicate comments:
In `@package.json`:
- Line 71: The dependency entry for "@angular/platform-server" is using tilde
pinning ("~21.2.6") while the rest of the Angular runtime deps are exact-pinned
("21.2.6"), causing inconsistent version strategy; update the
"@angular/platform-server" entry in package.json to use the exact version
"21.2.6" to match the other `@angular/`* runtime packages (or, if tilde pinning is
intentional, add a short documented justification in the repo docs and a CI note
referencing "@angular/platform-server" to explain the exemption).

In `@packages/router/src/lib/provide-file-router-base.ts`:
- Around line 122-139: The DEV-only early return in the APP_BOOTSTRAP_LISTENER
factory disables updateMetaTagsOnRouteChange and updateJsonLdOnRouteChange
during development; remove that import.meta.env.DEV guard (or replace it with a
configurable flag) so the factory always wires the router subscriptions, and
stop deferring subscription setup via queueMicrotask—create the
Router/Meta/DOCUMENT injections and call updateMetaTagsOnRouteChange and
updateJsonLdOnRouteChange synchronously (or schedule them after setting up
subscriptions but not via queueMicrotask) so the subscription is active before
the first NavigationEnd; ensure changes are applied in the factory that returns
the bootstrap callback (referencing provide: APP_BOOTSTRAP_LISTENER, useFactory,
updateMetaTagsOnRouteChange, updateJsonLdOnRouteChange, queueMicrotask,
inject(Router), inject(Meta), inject(DOCUMENT)).

---

Nitpick comments:
In `@apps/tailwind-debug-app/src/app/probes/style-probe.component.spec.ts`:
- Around line 23-33: Rename the test description to accurately reflect what it's
asserting: change the it(...) title in style-probe.component.spec.ts from
"preserves local state across css-only assertions" to something like "increments
and displays click count" so it clearly documents that it calls
StyleProbeComponent.increment() and checks the probe-counter text content;
update any related test comments to match the new name.

In `@packages/platform/src/lib/content-plugin.ts`:
- Around line 194-198: The debug event currently emits the raw path variable
which can vary by OS; change the payload to send the already-computed
normalizedPath instead. Locate the server.ws.send call
(server.ws.send('analog:debug-full-reload', {...})) in the content-plugin code
and replace the path property with path: normalizedPath (or rename to
normalizedPath in the payload if you prefer) so the debug payload consistently
uses the normalizedPath value.

In `@packages/platform/src/lib/utils/debug.spec.ts`:
- Around line 116-120: Add an explicit assertion that the first argument passed
to wrapInstancesForFileLog is the debug instances array to prevent silent
argument reordering: after calling applyDebugOption({ logFile: true },
'/custom/root') and grabbing callArgs from
vi.mocked(wrapInstancesForFileLog).mock.calls[0], assert something about
callArgs[0] (e.g., that it's an array and contains the expected debug instance
shape or length) before asserting callArgs[1] contains '/custom/root'; reference
wrapInstancesForFileLog and applyDebugOption to locate where to add this check
in the test.

In `@packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts`:
- Around line 24-33: Add a new unit test in stylesheet-registry.spec.ts that
exercises rewriteRelativeCssImports with the `@import` url(...) form: call
rewriteRelativeCssImports with a CSS string containing `@import`
url("./submenu/submenu.component.css"); and the same base path used in the
existing test (e.g. '/project/src/app/header.component.css'), and assert the
returned CSS rewrites the relative url to the absolute path
'/project/src/app/submenu/submenu.component.css' while leaving the rest of the
CSS intact; ensure the test name clearly states it covers the url(...) import
form.
🪄 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: afd51ea9-9ce6-4bc2-90f9-68b730ddc2dd

📥 Commits

Reviewing files that changed from the base of the PR and between 4d8844b and 7c9c2e9.

⛔ Files ignored due to path filters (8)
  • .dagger/src/index.ts is excluded by none and included by none
  • .gitignore is excluded by none and included by none
  • apps/tailwind-debug-app/public/analog.svg is excluded by !**/*.svg and included by apps/**
  • apps/tailwind-debug-app/public/favicon.ico is excluded by !**/*.ico and included by apps/**
  • changes/2026-04-04-reset-investigation/README.md is excluded by none and included by none
  • oxlint.config.ts is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
  • tests/vitest-angular/src/snapshot-serializers/__snapshots__/angular-fixture-snapshot.spec.ts.snap is excluded by !**/*.snap and included by tests/**
📒 Files selected for processing (137)
  • .github/workflows/ci.yml
  • apps/analog-app-e2e/tests/legacy-server-action.spec.ts
  • apps/analog-app/oxlint.config.ts
  • apps/analog-app/package.json
  • apps/analog-app/src/app/app.config.ts
  • apps/analog-app/src/app/pages/client/(client).page.ts
  • apps/analog-app/src/app/pages/legacy-action.page.ts
  • apps/analog-app/src/app/pages/legacy-action.server.ts
  • apps/analog-app/src/app/pages/newsletter.page.ts
  • apps/analog-app/src/app/pages/newsletter.server.ts
  • apps/analog-app/src/routeTree.gen.ts
  • apps/analog-app/vite.config.ts
  • apps/blog-app/src/app/app.config.ts
  • apps/blog-app/src/routeTree.gen.ts
  • apps/docs-app/docs/guides/idiomatic.md
  • apps/docs-app/docs/guides/migrating.md
  • apps/docs-app/docs/integrations/angular-material/index.md
  • apps/docs-app/docs/integrations/storybook/index.md
  • apps/docs-app/docs/packages/create-analog/overview.md
  • apps/docs-app/docs/packages/vite-plugin-angular/css-preprocessors.md
  • apps/docs-app/sidebars.js
  • apps/opt-catchall-app/src/app/pages/docs.page.ts
  • apps/opt-catchall-app/src/app/pages/docs/[[...slug]].page.ts
  • apps/opt-catchall-app/src/app/pages/docs/index.page.ts
  • apps/tailwind-debug-app-e2e/package.json
  • apps/tailwind-debug-app-e2e/playwright.config.ts
  • apps/tailwind-debug-app-e2e/project.json
  • apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts
  • apps/tailwind-debug-app/eslint.config.mjs
  • apps/tailwind-debug-app/index.html
  • apps/tailwind-debug-app/package.json
  • apps/tailwind-debug-app/postcss.config.mjs
  • apps/tailwind-debug-app/project.json
  • apps/tailwind-debug-app/public/.gitkeep
  • apps/tailwind-debug-app/src/app/app.component.css
  • apps/tailwind-debug-app/src/app/app.component.spec.ts
  • apps/tailwind-debug-app/src/app/app.component.ts
  • apps/tailwind-debug-app/src/app/app.config.server.ts
  • apps/tailwind-debug-app/src/app/app.config.ts
  • apps/tailwind-debug-app/src/app/debug/debug-stream.service.ts
  • apps/tailwind-debug-app/src/app/debug/debug-stream.shared.ts
  • apps/tailwind-debug-app/src/app/debug/hmr-diagnostics.spec.ts
  • apps/tailwind-debug-app/src/app/debug/hmr-diagnostics.ts
  • apps/tailwind-debug-app/src/app/pages/(home).page.ts
  • apps/tailwind-debug-app/src/app/pages/analog-welcome.component.ts
  • apps/tailwind-debug-app/src/app/pages/probe.page.ts
  • apps/tailwind-debug-app/src/app/probes/debug-stream-panel.component.ts
  • apps/tailwind-debug-app/src/app/probes/style-probe.component.css
  • apps/tailwind-debug-app/src/app/probes/style-probe.component.spec.ts
  • apps/tailwind-debug-app/src/app/probes/style-probe.component.ts
  • apps/tailwind-debug-app/src/app/probes/tailwind-debug-shell.component.css
  • apps/tailwind-debug-app/src/app/probes/tailwind-debug-shell.component.ts
  • apps/tailwind-debug-app/src/main.server.ts
  • apps/tailwind-debug-app/src/main.ts
  • apps/tailwind-debug-app/src/routeTree.gen.ts
  • apps/tailwind-debug-app/src/server/routes/api/v1/hello.ts
  • apps/tailwind-debug-app/src/server/routes/api/ws/debug-stream.ts
  • apps/tailwind-debug-app/src/styles.css
  • apps/tailwind-debug-app/src/test-setup.ts
  • apps/tailwind-debug-app/src/vite-env.d.ts
  • apps/tailwind-debug-app/tsconfig.app.json
  • apps/tailwind-debug-app/tsconfig.editor.json
  • apps/tailwind-debug-app/tsconfig.json
  • apps/tailwind-debug-app/tsconfig.spec.json
  • apps/tailwind-debug-app/vite.config.ts
  • apps/tanstack-query-app/vite.config.ts
  • libs/my-package/package.json
  • libs/my-package/src/lib/my-package/my-package.spec.ts
  • libs/shared/feature/src/pages/blog/posts.[slug].page.ts
  • nx.json
  • package.json
  • packages/astro-angular/oxlint.config.ts
  • packages/content/devtools/src/index.ts
  • packages/content/mdc/src/lib/mdc-renderer.directive.spec.ts
  • packages/content/oxlint.config.ts
  • packages/content/package.json
  • packages/content/src/index.spec.ts
  • packages/content/src/index.ts
  • packages/content/src/lib/anchor-navigation.directive.spec.ts
  • packages/content/vite.config.lib.ts
  • packages/create-analog/__tests__/cli.spec.ts
  • packages/create-analog/index.js
  • packages/nx-plugin/src/generators/app/generator.spec.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-config.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts
  • packages/nx-plugin/src/generators/app/versions/nx_18_X/versions.ts
  • packages/nx-plugin/src/generators/app/versions/tailwind-dependencies.ts
  • packages/platform/oxlint.config.ts
  • packages/platform/src/lib/content-plugin.ts
  • packages/platform/src/lib/options.ts
  • packages/platform/src/lib/platform-plugin.ts
  • packages/platform/src/lib/route-idiom-diagnostics.spec.ts
  • packages/platform/src/lib/route-idiom-diagnostics.ts
  • packages/platform/src/lib/router-plugin.spec.ts
  • packages/platform/src/lib/router-plugin.ts
  • packages/platform/src/lib/utils/debug.spec.ts
  • packages/router/oxlint.config.ts
  • packages/router/server/actions/src/page-server-action-compat.spec.ts
  • packages/router/server/src/render.spec.ts
  • packages/router/src/lib/debug/debug.page.ts
  • packages/router/src/lib/form-action.directive.spec.ts
  • packages/router/src/lib/json-ld.spec.ts
  • packages/router/src/lib/json-ld.ts
  • packages/router/src/lib/meta-tags.spec.ts
  • packages/router/src/lib/meta-tags.ts
  • packages/router/src/lib/provide-file-router-base.ts
  • packages/router/src/lib/provide-file-router.spec.ts
  • packages/router/src/lib/route-builder.ts
  • packages/router/src/lib/routes.spec.ts
  • packages/storybook-angular/README.md
  • packages/storybook-angular/oxlint.config.ts
  • packages/storybook-angular/src/lib/preset.spec.ts
  • packages/storybook-angular/src/lib/preset.ts
  • packages/storybook-angular/src/types.ts
  • packages/vite-plugin-angular/oxlint.config.ts
  • packages/vite-plugin-angular/src/lib/angular-jit-plugin.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin-live-reload.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
  • packages/vite-plugin-angular/src/lib/angular-vitest-plugin.spec.ts
  • packages/vite-plugin-angular/src/lib/component-resolvers.spec.ts
  • packages/vite-plugin-angular/src/lib/component-resolvers.ts
  • packages/vite-plugin-angular/src/lib/host.spec.ts
  • packages/vite-plugin-angular/src/lib/host.ts
  • packages/vite-plugin-angular/src/lib/plugins/file-replacements.plugin.ts
  • packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts
  • packages/vite-plugin-angular/src/lib/stylesheet-registry.ts
  • packages/vite-plugin-angular/src/lib/utils/debug.spec.ts
  • packages/vite-plugin-angular/src/lib/utils/debug.ts
  • packages/vite-plugin-angular/src/lib/utils/devkit.ts
  • packages/vite-plugin-nitro/src/index.ts
  • packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.spec.ts
  • packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.ts
  • pnpm-workspace.yaml
  • tests/vitest-angular/src/snapshot-serializers/angular-fixture-snapshot.spec.ts
  • tools/scripts/debug-nx-project-graph.mts
  • tools/scripts/verify-route-freshness.mts
💤 Files with no reviewable changes (3)
  • apps/analog-app/src/app/pages/newsletter.page.ts
  • apps/tanstack-query-app/vite.config.ts
  • packages/content/src/index.ts
✅ Files skipped from review due to trivial changes (46)
  • apps/tailwind-debug-app-e2e/package.json
  • apps/analog-app/package.json
  • apps/opt-catchall-app/src/app/pages/docs/index.page.ts
  • apps/analog-app-e2e/tests/legacy-server-action.spec.ts
  • apps/opt-catchall-app/src/app/pages/docs.page.ts
  • apps/tailwind-debug-app/index.html
  • apps/tailwind-debug-app/src/app/app.component.spec.ts
  • apps/tailwind-debug-app/postcss.config.mjs
  • libs/my-package/package.json
  • libs/my-package/src/lib/my-package/my-package.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vitest-plugin.spec.ts
  • libs/shared/feature/src/pages/blog/posts.[slug].page.ts
  • packages/nx-plugin/src/generators/app/versions/nx_18_X/versions.ts
  • packages/content/mdc/src/lib/mdc-renderer.directive.spec.ts
  • apps/tailwind-debug-app/src/vite-env.d.ts
  • apps/tailwind-debug-app/src/app/pages/analog-welcome.component.ts
  • apps/docs-app/docs/integrations/storybook/index.md
  • packages/router/src/lib/form-action.directive.spec.ts
  • apps/docs-app/docs/guides/idiomatic.md
  • apps/opt-catchall-app/src/app/pages/docs/[[...slug]].page.ts
  • apps/tailwind-debug-app/src/app/app.component.ts
  • apps/docs-app/docs/packages/vite-plugin-angular/css-preprocessors.md
  • packages/router/src/lib/provide-file-router.spec.ts
  • packages/router/src/lib/routes.spec.ts
  • packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.spec.ts
  • apps/tailwind-debug-app/src/app/probes/tailwind-debug-shell.component.css
  • apps/tailwind-debug-app/src/styles.css
  • apps/tailwind-debug-app/tsconfig.app.json
  • apps/tailwind-debug-app/tsconfig.json
  • apps/tailwind-debug-app/tsconfig.editor.json
  • apps/tailwind-debug-app/src/app/debug/hmr-diagnostics.spec.ts
  • apps/tailwind-debug-app-e2e/project.json
  • packages/content/src/index.spec.ts
  • tests/vitest-angular/src/snapshot-serializers/angular-fixture-snapshot.spec.ts
  • packages/content/vite.config.lib.ts
  • packages/vite-plugin-angular/src/lib/utils/devkit.ts
  • packages/content/devtools/src/index.ts
  • tools/scripts/debug-nx-project-graph.mts
  • packages/create-analog/index.js
  • pnpm-workspace.yaml
  • packages/vite-plugin-angular/src/lib/angular-jit-plugin.ts
  • packages/storybook-angular/README.md
  • nx.json
  • packages/router/server/actions/src/page-server-action-compat.spec.ts
  • apps/tailwind-debug-app/package.json
  • packages/vite-plugin-nitro/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (21)
  • apps/analog-app/vite.config.ts
  • apps/analog-app/src/app/pages/newsletter.server.ts
  • apps/blog-app/src/app/app.config.ts
  • apps/docs-app/docs/packages/create-analog/overview.md
  • packages/platform/src/lib/options.ts
  • packages/storybook-angular/src/lib/preset.spec.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts
  • packages/platform/src/lib/platform-plugin.ts
  • packages/router/src/lib/route-builder.ts
  • packages/vite-plugin-angular/src/lib/plugins/file-replacements.plugin.ts
  • packages/storybook-angular/src/lib/preset.ts
  • packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.ts
  • apps/analog-app/src/app/pages/legacy-action.page.ts
  • apps/analog-app/src/app/pages/legacy-action.server.ts
  • apps/analog-app/src/app/pages/client/(client).page.ts
  • packages/nx-plugin/src/generators/app/versions/tailwind-dependencies.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin-live-reload.spec.ts
  • packages/vite-plugin-angular/src/lib/host.spec.ts
  • packages/vite-plugin-angular/src/lib/utils/debug.spec.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-config.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts

Copy link
Copy Markdown

@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 (1)
packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts (1)

1166-1201: hasComponent detection tests document expected behavior across compilation paths.

These tests serve primarily as documentation—they verify string-based @Component detection works on raw TS but fails on compiled output (where decorators become ɵɵdefineComponent calls). This is useful for understanding the useAngularCompilationAPI behavioral difference but doesn't exercise actual plugin code.

Consider adding a comment noting this is behavioral documentation rather than a regression guard, or extracting into a dedicated *.doc.spec.ts if the pattern grows.

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

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts` around
lines 1166 - 1201, The tests under "hasComponent detection" are documentation of
expected behavior across compilation paths rather than strict regression tests;
update the suite to make that explicit by either (A) adding a short clarifying
comment and adjusting the it() titles to state "documentation" (e.g., include
"documentation-only" or "behavior doc") or (B) move these two specs into a
dedicated doc-spec file (e.g., name ending in .doc.spec.ts) to separate
behavioral documentation from regression tests; target the describe name
"hasComponent detection", the sample variables rawTs and compiledJs, and the
reference to useAngularCompilationAPI/ɵɵdefineComponent when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts`:
- Around line 1166-1201: The tests under "hasComponent detection" are
documentation of expected behavior across compilation paths rather than strict
regression tests; update the suite to make that explicit by either (A) adding a
short clarifying comment and adjusting the it() titles to state "documentation"
(e.g., include "documentation-only" or "behavior doc") or (B) move these two
specs into a dedicated doc-spec file (e.g., name ending in .doc.spec.ts) to
separate behavioral documentation from regression tests; target the describe
name "hasComponent detection", the sample variables rawTs and compiledJs, and
the reference to useAngularCompilationAPI/ɵɵdefineComponent when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81c35074-527e-43e6-a131-bd4c171fb776

📥 Commits

Reviewing files that changed from the base of the PR and between 7c9c2e9 and 7eafecb.

📒 Files selected for processing (6)
  • packages/router/src/lib/provide-file-router-base.ts
  • packages/router/src/lib/provide-file-router.spec.ts
  • packages/router/src/lib/route-builder.ts
  • packages/router/src/lib/routes.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/router/src/lib/routes.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/router/src/lib/provide-file-router-base.ts
  • packages/router/src/lib/route-builder.ts

Copy link
Copy Markdown

@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 (1)
packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts (1)

1233-1259: Replace these tautological checks with real useAngularCompilationAPI coverage.

Both assertions only prove that the hand-written fixtures do or do not contain the substring @Component; they never execute the plugin/helper that gates this branch. As written, they will keep passing even if the actual compiled-vs-raw detection regresses.

As per coding guidelines, **/*.spec.{ts,tsx}: Keep tests lightweight and targeted to critical functionality testing.

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

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts` around
lines 1233 - 1259, The two tests currently only check string contents of rawTs
and compiledJs; replace the tautological expects with calls to the actual
detection helper used by the plugin (e.g., hasComponent or the plugin's
detection function) so you assert behavior of the real code path. Specifically,
in the "hasComponent detection" suite, call the exported detection function with
rawTs and compiledJs (or run the plugin transform hook that uses
useAngularCompilationAPI) and assert it returns true for the raw TypeScript
fixture and false for the compiled output; keep the existing fixtures rawTs and
compiledJs and update the it() blocks to invoke and assert the real helper
rather than using .includes('@Component').
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts`:
- Around line 1233-1259: The two tests currently only check string contents of
rawTs and compiledJs; replace the tautological expects with calls to the actual
detection helper used by the plugin (e.g., hasComponent or the plugin's
detection function) so you assert behavior of the real code path. Specifically,
in the "hasComponent detection" suite, call the exported detection function with
rawTs and compiledJs (or run the plugin transform hook that uses
useAngularCompilationAPI) and assert it returns true for the raw TypeScript
fixture and false for the compiled output; keep the existing fixtures rawTs and
compiledJs and update the it() blocks to invoke and assert the real helper
rather than using .includes('@Component').

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6cfcd1a-cc03-4a2c-9fce-33483d14bddf

📥 Commits

Reviewing files that changed from the base of the PR and between 7eafecb and 8a93d0c.

📒 Files selected for processing (2)
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (3)
packages/platform/src/lib/router-plugin.ts (1)

239-243: Verify parent layout diagnostics are refreshed after child route add/unlink.

On add/unlink, discovery caches are invalidated (line 236) so subsequent discoverPageRouteFiles() calls return fresh results. However, diagnostics are only run for the touched file — existing parent layouts (e.g., products.page.ts) won't see their layout-without-router-outlet diagnostic re-evaluated until they're edited or the server restarts.

The past review comment flagged this, and it was marked as addressed. If the current behavior is intentional (defer parent rescan to next edit/restart for performance), consider documenting this limitation in a code comment for future maintainers.

📝 Optional: Document diagnostic rescan scope
           if (event === 'unlink') {
             routeDiagnosticCache.delete(path);
           } else {
+            // Only the touched file is re-analyzed for performance; parent
+            // layouts get rechecked on their next edit or server restart.
             reportRouteDiagnostics(path);
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/platform/src/lib/router-plugin.ts` around lines 239 - 243, The
current add/unlink branch only removes the single-file cache and runs
reportRouteDiagnostics(path), which leaves parent layouts (e.g.,
products.page.ts) with stale "layout-without-router-outlet" diagnostics; update
the handler so after invalidating routeDiagnosticCache you call
discoverPageRouteFiles() to get fresh routes and then run reportRouteDiagnostics
for any affected parent/layout files (or at minimum iterate discovered routes
and call reportRouteDiagnostics on parent layout files) to re-evaluate layout
diagnostics; if this behavior is intentionally deferred for performance, add a
clear comment above the add/unlink branch referencing routeDiagnosticCache,
discoverPageRouteFiles(), and reportRouteDiagnostics explaining the limitation
and why parent rescans are deferred.
packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts (2)

925-1003: Duplicate selector/class name tests rely on module-scoped state without isolation.

These tests call transform multiple times and expect the plugin's "active graph" to accumulate selectors and class names across calls. This works because the plugin maintains module-level state, but:

  1. Tests in this suite share state with each other (e.g., 'demo-card' selector registered at line 940 affects line 948's duplicate check).
  2. If test order changes or tests run in parallel, this could cause flaky failures.
  3. Earlier tests in this file (like 'throws for selectorless non-page components' at line 850) also call transform and may pollute the active graph for later tests.

Consider resetting the active graph state between tests or extracting a fresh plugin instance per test to ensure isolation.

💡 Suggested pattern for test isolation
   it('throws for duplicate selectors in the active graph', () => {
+    // Get a fresh plugin instance to avoid state pollution from other tests
     const plugin = angular().find(
       (p) =>
         p.name === '@analogjs/vite-plugin-angular:template-class-binding-guard',
     ) as Plugin;

     const transform =
       typeof plugin.transform === 'function'
         ? plugin.transform
         : (plugin.transform as any)?.handler;

+    // First transform registers the selector
     transform.call(
       { warn: vi.fn() } as any,
       `
         `@Component`({
           selector: 'demo-card',

Alternatively, if the plugin exposes a reset method or the state is accessible, add cleanup in afterEach:

afterEach(() => {
  // Reset any module-scoped state in the plugin
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts` around
lines 925 - 1003, Tests rely on module-scoped "active graph" state inside the
template-class-binding-guard plugin (the transform function returned from
angular() / plugin name
'@analogjs/vite-plugin-angular:template-class-binding-guard'), causing
cross-test pollution and flakiness; fix by isolating state between tests: either
ensure each test uses a fresh plugin instance by re-importing/creating plugin
via angular() inside the test or add an afterEach that resets the module-level
state (call any exposed reset method on the guard, or use the test runtime to
clear module cache like vi.resetModules() so a new transform/active graph is
created for each test). Ensure the chosen approach targets the transform handler
returned from angular() so duplicate selector/class-name checks no longer share
state across tests.

416-442: Add error path test coverage for refreshStylesheetRegistryForFile.

The function reads the source file from disk without a try-catch wrapper. While existsSync provides an upfront guard, a race condition is theoretically possible in watch mode if the file is deleted between the check and the read. The test suite only covers the happy path; consider adding a test case for file read failures (e.g., ENOENT or permission errors). This is minor since HMR watches for style files infrequently encounter such errors, but aligns with error handling patterns elsewhere in this file.

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

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts` around
lines 416 - 442, Add a failing-read test for refreshStylesheetRegistryForFile
that simulates the race where existsSync passes but the subsequent read fails:
create an AnalogStylesheetRegistry, register a served stylesheet (using
registerServedStylesheet with a temp file path and initial normalizedCode), then
stub/mock fs.readFileSync (or the underlying file read used by
refreshStylesheetRegistryForFile) to throw an ENOENT or permission error when
called, invoke refreshStylesheetRegistryForFile with the same path, and assert
the registry’s served content (getServedContent for the publicId) remains
unchanged (or matches the intended error-path behavior); ensure the stub is
restored/cleaned up and any temp files are removed in finally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 145-147: The pnpm v10 allowlist is missing the package with
lifecycle scripts: add "@swc/core" to the pnpm.onlyBuiltDependencies array in
package.json so its postinstall runs and native binaries are initialized; locate
the pnpm.onlyBuiltDependencies list in package.json and include the "@swc/core"
entry alongside the existing entries (the diff shows "@swc-node/register" and
"@swc/helpers" already present).

---

Nitpick comments:
In `@packages/platform/src/lib/router-plugin.ts`:
- Around line 239-243: The current add/unlink branch only removes the
single-file cache and runs reportRouteDiagnostics(path), which leaves parent
layouts (e.g., products.page.ts) with stale "layout-without-router-outlet"
diagnostics; update the handler so after invalidating routeDiagnosticCache you
call discoverPageRouteFiles() to get fresh routes and then run
reportRouteDiagnostics for any affected parent/layout files (or at minimum
iterate discovered routes and call reportRouteDiagnostics on parent layout
files) to re-evaluate layout diagnostics; if this behavior is intentionally
deferred for performance, add a clear comment above the add/unlink branch
referencing routeDiagnosticCache, discoverPageRouteFiles(), and
reportRouteDiagnostics explaining the limitation and why parent rescans are
deferred.

In `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts`:
- Around line 925-1003: Tests rely on module-scoped "active graph" state inside
the template-class-binding-guard plugin (the transform function returned from
angular() / plugin name
'@analogjs/vite-plugin-angular:template-class-binding-guard'), causing
cross-test pollution and flakiness; fix by isolating state between tests: either
ensure each test uses a fresh plugin instance by re-importing/creating plugin
via angular() inside the test or add an afterEach that resets the module-level
state (call any exposed reset method on the guard, or use the test runtime to
clear module cache like vi.resetModules() so a new transform/active graph is
created for each test). Ensure the chosen approach targets the transform handler
returned from angular() so duplicate selector/class-name checks no longer share
state across tests.
- Around line 416-442: Add a failing-read test for
refreshStylesheetRegistryForFile that simulates the race where existsSync passes
but the subsequent read fails: create an AnalogStylesheetRegistry, register a
served stylesheet (using registerServedStylesheet with a temp file path and
initial normalizedCode), then stub/mock fs.readFileSync (or the underlying file
read used by refreshStylesheetRegistryForFile) to throw an ENOENT or permission
error when called, invoke refreshStylesheetRegistryForFile with the same path,
and assert the registry’s served content (getServedContent for the publicId)
remains unchanged (or matches the intended error-path behavior); ensure the stub
is restored/cleaned up and any temp files are removed in finally.
🪄 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: 1eab99b0-0860-43c8-87b5-e9603dbeb914

📥 Commits

Reviewing files that changed from the base of the PR and between 8a93d0c and 135c883.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (6)
  • package.json
  • packages/platform/src/lib/router-plugin.spec.ts
  • packages/platform/src/lib/router-plugin.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (1)
  • pnpm-workspace.yaml

@benpsnyder
Copy link
Copy Markdown
Contributor Author

@coderabbitai
I verified the current code against the remaining review findings and only kept the fixes that were still needed.

Still-valid findings that I fixed:

  • apps/docs-app/docs/guides/migrating.md: added the Angular version compatibility note so the hmr / liveReload section now states that Analog requires Angular v19+, and that Angular v16-v18 disable HMR at runtime with a console warning.
  • apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts: changed teardown to restore the exact checked-in stylesheet contents instead of resetting to a hard-coded class.
  • apps/tailwind-debug-app/project.json: made serve-nitro depend on build so the expected dist output exists before serving.
  • apps/tailwind-debug-app/src/app/app.component.css: suppressed the scss/at-rule-no-unknown warning for Tailwind v4 @reference.
  • apps/tailwind-debug-app/src/app/debug/debug-stream.service.ts: deduplicates merged stream entries by id before trimming so snapshots do not re-add duplicates.
  • apps/tailwind-debug-app/src/app/debug/hmr-diagnostics.ts: wraps persistent storage writes in try/catch and warns without breaking in-memory diagnostics.
  • apps/tailwind-debug-app/src/app/probes/debug-stream-panel.component.ts: Clear now clears both the live stream and the frozen paused view.
  • packages/platform/src/lib/router-plugin.ts: add/unlink route changes now rescan all surviving .page.ts files for diagnostics, with regression coverage in packages/platform/src/lib/router-plugin.spec.ts.
  • packages/vite-plugin-angular/src/lib/component-resolvers.ts: component metadata extraction is now limited to actual @Component(...) decorator objects, with regression coverage in packages/vite-plugin-angular/src/lib/component-resolvers.spec.ts.
  • apps/tailwind-debug-app/src/app/probes/style-probe.component.spec.ts: renamed the test so the title matches what it actually asserts.
  • packages/platform/src/lib/content-plugin.ts: debug reload payloads now emit normalizedPath instead of the raw path.
  • packages/platform/src/lib/utils/debug.spec.ts: added an explicit assertion for the first wrapInstancesForFileLog argument.
  • packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts: added coverage for rewriting @import url(...) CSS imports.
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts: updated the behavior-doc test naming/commentary for the hasComponent detection nitpick.

Findings that were already resolved and did not need another fix:

  • package.json: the @angular/platform-server comment was already superseded because the root dependencies now come from the workspace catalog.
  • packages/router/src/lib/provide-file-router-base.ts: the dev-only bootstrap short-circuit had already been removed, and the metadata subscriptions are initialized during app init.

Targeted verification run on this branch:

  • pnpm vitest run packages/vite-plugin-angular/src/lib/component-resolvers.spec.ts packages/vite-plugin-angular/src/lib/stylesheet-registry.spec.ts packages/platform/src/lib/router-plugin.spec.ts packages/platform/src/lib/utils/debug.spec.ts
  • pnpm nx test tailwind-debug-app --runInBand
  • pnpm vitest run --config packages/vite-plugin-angular/vite.config.ts packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts
  • pnpm oxlint -c oxlint.config.ts --quiet ... on the touched TS files: 0 errors

Follow-up commits:

  • fb701b8 docs: note Angular HMR version requirements
  • 9c6a296 fix: harden tailwind debug app HMR diagnostics
  • 779c9c8 fix(platform): refresh route diagnostics on route graph changes
  • c37ff35 fix(vite-plugin-angular): restrict metadata extraction to component decorators
  • bcadc13 test(vite-plugin-angular): tighten resolver coverage docs

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Tip

For best results, initiate chat on the files or code changes.

@benpsnyder — thanks for the detailed accounting. Here's a clean summary of what I'm now tracking:

Confirmed addressed in this round (14 fixes + 2 already-resolved):

Item Status
migrating.md — Angular v19+ HMR requirement note ✅ Fixed
component-css-hmr.spec.ts — teardown restores exact checked-in stylesheet ✅ Fixed
tailwind-debug-app/project.jsonserve-nitro depends on build ✅ Fixed
app.component.cssscss/at-rule-no-unknown suppressed for @reference ✅ Fixed
debug-stream.service.ts — deduplicates by id before trimming ✅ Fixed
hmr-diagnostics.ts — storage writes wrapped in try/catch ✅ Fixed
debug-stream-panel.component.ts — Clear clears both live and frozen view ✅ Fixed
router-plugin.ts — add/unlink rescans all surviving .page.ts files ✅ Fixed
component-resolvers.ts — metadata extraction restricted to @Component(...) decorators ✅ Fixed
style-probe.component.spec.ts — test title aligned with actual assertion ✅ Fixed
content-plugin.ts — debug reload payloads emit normalizedPath ✅ Fixed
utils/debug.spec.ts — explicit assertion for first wrapInstancesForFileLog arg ✅ Fixed
stylesheet-registry.spec.ts — coverage for @import url(...) rewriting ✅ Fixed
angular-vite-plugin.spec.ts — behavior-doc test naming updated ✅ Fixed
package.json@angular/platform-server comment (superseded by workspace catalog) ✅ Already resolved
provide-file-router-base.ts — bootstrap short-circuit (already removed) ✅ Already resolved

Still outstanding:

  • ⏳ Playwright E2E suite (analog-app-e2e) — pre-existing json-ld.spec.ts failure still present; new regressions in the stylesheet/HMR path won't be visible until a full run.
  • ⏳ brandonroberts's "should fix before merge" items — tag-matching regex false positives (#1/[FIX]: Speed up Angular Vite Plugin #2), investigation file (#10), and breaking change label for the @analogjs/content devtools export removal. These were noted but not addressed in this update — flagging them in case they need a pass before merge.

✏️ Learnings added
Learnt from: benpsnyder
URL: https://github.com/analogjs/analog/pull/2226

Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `packages/vite-plugin-angular/src/lib/component-resolvers.ts`, `getAngularComponentMetadata()` (and the internal `collectComponentUrls` helper) is restricted to actual `Component(...)` decorator objects only. Do not flag or suggest reverting this restriction — it was added deliberately (PR `#2226`, commit c37ff35f2) to prevent false-positive metadata extraction from non-component decorators.

Learnt from: benpsnyder
URL: https://github.com/analogjs/analog/pull/2226

Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `apps/docs-app/docs/guides/migrating.md` (PR `#2226`), the `hmr`/`liveReload` HMR section now documents that Analog's Angular HMR requires Angular v19+. Angular v16–v18 will disable HMR at runtime and emit a console warning. Do not flag this warning as unnecessary — it is the designed diagnostic for the Angular version constraint, consistent with the warning in `angular-vite-plugin.ts`.

Learnt from: benpsnyder
URL: https://github.com/analogjs/analog/pull/2226

Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `packages/platform/src/lib/router-plugin.ts` (PR `#2226`, commit 779c9c8f4), `add` and `unlink` watcher events now trigger a full rescan of all surviving `.page.ts` files for route idiom diagnostics (not just the file that changed). This is intentional: removing or adding a file can affect layout detection for sibling routes. Do not suggest scoping the rescan back to just the changed file.

Learnt from: benpsnyder
URL: https://github.com/analogjs/analog/pull/2226

Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `packages/platform/src/lib/content-plugin.ts` (PR `#2226`), the `analog:debug-full-reload` WebSocket payload emits `normalizedPath` (not the raw watcher-provided path). Do not flag the normalized form as incorrect or suggest reverting to the raw path.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-03-25T07:19:07.510Z
Learning: In `analogjs/analog`, the `e2e:analog` suite (`apps/analog-app-e2e` / `json-ld.spec.ts`) has a pre-existing failure introduced in commit `9ed63a6b` (typed routes + JSON-LD): the prerender step produces empty `<analogjs-root></analogjs-root>` because Angular SSR does not execute during Nitro's prerender phase, so JSON-LD scripts are never injected. This failure is present on the `alpha` branch and is not caused by the `feat/investigate-nitro-vite-plugin` migration (PR `#2188`).

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-04T19:18:17.239Z
Learning: In `packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`, `angularVitestPlugins()` helpers are NOT the HMR toggle. HMR is controlled by dedicated HMR helper plugins gated by `shouldEnableHmr()`. Tests for the HMR option (`hmr: false`, `liveReload: true`, etc.) must assert against the HMR helper plugins under a development-like env, not against the always-present Vitest plugins. See PR `#2226`, `packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts` line 24 for the corrected test pattern.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-04T19:18:17.239Z
Learning: In `packages/vite-plugin-angular/src/lib/stylesheet-registry.ts`, the basename alias for stylesheet registry lookups was intentionally removed because it is collision-prone. The module also rewrites `import url(...)` CSS import forms (not just bare `import "..."` forms) to absolute paths. Do not suggest restoring the basename alias or flag the url()-form rewrite as missing.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/router/src/lib/route-manifest.spec.ts:723-734
Timestamp: 2026-03-24T13:22:40.861Z
Learning: In `packages/router/src/lib/route-manifest.spec.ts` (and the corresponding `detectSchemaExports` implementation in `packages/router/manifest/src/index.ts`), the regex-based schema export detector intentionally false-positives on commented-out `export const routeParamsSchema`/`export const routeQuerySchema` lines. This is an accepted v1 limitation explicitly documented in the test. The test expectation `expect(result.hasParamsSchema).toBe(true)` for commented-out content is correct and intentional. Do not flag this as a bug or suggest changing the expectation to `false` — it will be addressed if/when schema detection is upgraded to AST-based parsing.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/platform/vite.config.lib.ts:0-0
Timestamp: 2026-03-22T23:12:26.945Z
Learning: In `tools/build/shared-plugins.ts` (and any package that uses it), the `oxcDtsPlugin` catch blocks around `isolatedDeclarationSync` calls were updated (PR `#2144`) to re-throw with file context on genuine failures. The normal "best-effort skip" path (file lacks explicit type annotations) is NOT an exception — `isolatedDeclarationSync` simply returns empty `code`, so the catch block change does not affect that case. Only the "already exists" probe catch around the secondary walk remains intentionally silent. Do not revert the catch blocks back to silent/empty form for the declaration-emission loops.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/vite-plugin-routes/src/lib/route-file-discovery.ts:53-83
Timestamp: 2026-03-24T13:23:00.699Z
Learning: In `packages/vite-plugin-routes/src/lib/route-file-discovery.ts`, `getDiscoveredFileKind` intentionally uses convention-based `includes()` checks (e.g. `normalized.includes('/src/app/pages/')`, `normalized.includes('/src/app/routes/')`, `normalized.includes('/src/content/')`) to classify standard/well-known dirs. This is acceptable because the dev watcher delivers absolute paths and those path segments are sufficiently specific. Configurable `additionalPagesDirs`/`additionalContentDirs` are already root-scoped via `isWithinDir()`. Do not flag the standard-dir `includes()` checks as a monorepo scope-leak risk — the design is intentional.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/content/vite.config.ts:24-26
Timestamp: 2026-03-24T13:22:20.254Z
Learning: In `packages/content/vite.config.ts`, the `resolve: { tsconfigPaths: true }` block is pre-existing dead config. `tsconfigPaths` is not a valid Vite `resolve` option and is silently ignored. The maintainer has explicitly decided not to fix this as it is unrelated to in-flight PRs. Do not flag this as an issue in future reviews of this file.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/vite-plugin-angular-tools/vite.config.lib.ts:10-11
Timestamp: 2026-03-22T22:11:42.083Z
Learning: In `packages/vite-plugin-angular-tools/vite.config.lib.ts` (and other `vite.config.lib.ts` build configs in analogjs/analog), the import of `oxcDtsPlugin` from `../../tools/build/shared-plugins.ts` uses a relative cross-package path with an `// eslint-disable-next-line nx/enforce-module-boundaries` suppression. This is intentional and correct: `tools/build/shared-plugins.ts` is a build-time utility with no registered TypeScript path alias in `tsconfig.base.json`. Do not flag this relative import or its eslint suppression as a violation — suggesting a non-existent workspace alias would break the import.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/router/manifest/src/index.ts:161-165
Timestamp: 2026-03-24T13:22:29.071Z
Learning: In `packages/router/manifest/src/index.ts`, `detectSchemaExports` intentionally uses simple line-start regexes (`/^\s*export\s+const\s+routeParamsSchema\b/m`) without stripping comments first. This is a known v1 limitation: a commented-out `// export const routeParamsSchema` will produce a false positive. The trade-off is explicitly accepted and documented in the test at `packages/router/src/lib/route-manifest.spec.ts` around line 723 ("Simple regex doesn't filter comments — acceptable for v1."). Do not flag this as a bug or propose AST-based detection; the fix is deferred to a future version.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/vitest-angular/vite.config.lib.ts:10-22
Timestamp: 2026-03-22T22:07:43.903Z
Learning: In `packages/vitest-angular/vite.config.lib.ts` (and other `vite.config.lib.ts` files in this repo), the `copyAssetsPlugin`'s `existsSync` guard is intentionally defensive/lenient. Missing required assets are caught downstream by `tools/scripts/verify-package-artifacts.mts`, which runs after the build and fails the pipeline if required artifacts are absent. Do not suggest making individual assets throw errors or adding `optional` flags to the copy loop — validation is intentionally centralized in the verify step, not in the copy plugins.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-04T19:18:17.239Z
Learning: In `packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`, when Angular <19 is detected, HMR is intentionally forced off and a visible `console.warn` is now emitted to surface this to developers in dev mode. This warning is not a bug — it is the designed diagnostic for the Angular version constraint. Do not flag the warn as unnecessary or suggest suppressing it.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/router/manifest/src/index.ts:300-311
Timestamp: 2026-03-24T18:37:45.091Z
Learning: In `packages/router/manifest/src/index.ts`, all filename arguments reaching `getCollisionPriority` (and any other path-checking helpers in that file) are already pre-normalized through Vite's `normalizePath`, which converts backslashes to forward slashes. Do not flag the `includes('/')` checks in these helpers as Windows-incompatible — slash normalization is guaranteed by the caller before these functions are invoked.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2226
File: packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts:1357-1359
Timestamp: 2026-04-04T19:28:54.429Z
Learning: In `packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`, the exported `normalizeIncludeGlob(workspaceRoot, glob)` helper normalizes `PluginOptions.include` globs using a three-way dispatch matching `normalizeWatchedDir` in `router-plugin.ts`: (1) already-rooted paths (starts with workspaceRoot) are returned unchanged; (2) `/`-prefixed workspace-relative paths are prepended with workspaceRoot; (3) bare relative globs are resolved via `resolve(workspaceRoot, glob)`. This was introduced in PR `#2226` (commit 20859121a01fdf01d18d8d87a4b4a921d3ecf67e). Do not flag the simple string-concatenation pattern as a bug — it no longer exists; `normalizeIncludeGlob` is used instead.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-03-25T07:19:07.510Z
Learning: In `packages/vite-plugin-nitro/src/lib/analog-vite-plugin.ts` (PR `#2188`), the `nitro/vite` plugin is restricted to serve mode only (i.e., not included during production builds). This is intentional to prevent conflicts with Nx executor output directory management during builds.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2216
File: packages/platform/src/lib/route-generation-plugin.ts:14-18
Timestamp: 2026-04-01T00:39:20.089Z
Learning: In `packages/platform/src/lib/route-generation-plugin.ts`, the `resolveTypedRouterOptions` function intentionally treats `undefined` (unset `experimental.typedRouter`) the same as `true`, both resolving to `{ enabled: true, options: { jsonLdManifest: true } }`. This is a deliberate breaking change introduced in PR `#2216`: typed route generation is now **on by default**. Users who want the previous opt-in behavior must explicitly set `experimental: { typedRouter: false }`. Do not flag this as a bug or suggest reverting to the old `if (!typedRouter)` guard — the change is intentional and migration/release notes are tracked separately.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/vitest-angular-tools/vite.config.lib.ts:30-34
Timestamp: 2026-03-22T23:32:34.648Z
Learning: In `packages/vitest-angular-tools/vite.config.lib.ts`, `build.emptyOutDir` is intentionally set to `false`. The output directory is `node_modules/analogjs/vitest-angular/src/lib/tools`, which is a subdirectory of the `vitest-angular` package output. Setting `emptyOutDir: true` would delete sibling files already written by the parent `vitest-angular` build (which runs immediately before `nx build vitest-angular-tools`). Do not flag this as a bug. The same pattern applies to `packages/vite-plugin-angular-tools/vite.config.lib.ts`.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/router/src/lib/route-path.typetest.spec.ts:62-155
Timestamp: 2026-03-24T13:22:48.562Z
Learning: In `packages/router/src/lib/route-path.typetest.spec.ts`, the `MockRouteTable` interface intentionally does NOT augment `AnalogRouteTable`. The file header states that since `AnalogRouteTable` is empty by default (populated only by generated code), type utility correctness is validated by testing conditional types directly against `MockRouteTable`. Do not flag this as a missing augmentation or suggest adding `declare module '...' { interface AnalogRouteTable extends MockRouteTable {} }`.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/create-analog/template-angular-v17/vite.config.ts:15-17
Timestamp: 2026-03-24T13:23:18.967Z
Learning: In `packages/create-analog/template-angular-v17/vite.config.ts` and `packages/create-analog/template-angular-v18/vite.config.ts`, the `__TAILWIND_PLUGIN__` placeholder is substituted by `create-analog/src/index.js` (around line 214) with either `'    tailwindcss(),\n'` (includes indentation, trailing comma, and newline) when Tailwind is selected, or `''` (empty string) when it is not. Because the trailing comma is part of the substitution value itself, the resulting `plugins` array is always valid JS after substitution. The raw template is intentionally not directly parseable as JS/TS before substitution. Do not report a syntax error for an apparent missing comma between `__TAILWIND_PLUGIN__` and `analog()` — the comma is supplied by the substitution value, not the template literal.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/vite-plugin-angular-tools/vite.config.lib.ts:30-34
Timestamp: 2026-03-22T22:06:57.824Z
Learning: In `packages/vite-plugin-angular-tools/vite.config.lib.ts`, `build.emptyOutDir` is intentionally set to `false`. The output directory is `node_modules/analogjs/vite-plugin-angular/src/lib/tools`, which is a subdirectory of the `vite-plugin-angular` package output. Setting `emptyOutDir: true` would delete sibling files already written by the parent `vite-plugin-angular` build, breaking the combined build pipeline. Do not flag this as a bug.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/content-plugin/tsdown.config.ts:24-24
Timestamp: 2026-03-22T22:10:37.534Z
Learning: In the analogjs/analog monorepo, `tsdown` (and Vite lib) build configs intentionally write their `outDir` directly into `node_modules/analogjs/<package>` (not `dist/`). This is the monorepo-wide convention that enables Nx caching and local dev package resolution without a separate `npm link` step. Do not flag this pattern as an antipattern or suggest routing output to `dist/` instead.

@benpsnyder
Copy link
Copy Markdown
Contributor Author

@coderabbitai 9e0be35 adjusted the repro to match tailwindlabs/tailwindcss#19903 instead of asserting the opposite behavior.

What changed:

  • apps/tailwind-debug-app/vite.config.ts
    • the HMR wiretap now logs both server.ws.send and environment.hot.send
    • full-reload payloads also capture a stack so the source matches the upstream report more closely
  • apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts
    • renamed the test to reflect that it reproduces the Tailwind-triggered full reload
    • stopped asserting “red without reload”
    • now asserts the actual repro signal:
      • the stylesheet edit is detected
      • environment.hot.send emits full-reload
      • app state resets after reload
      • the stack includes updateModules
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
    • unchanged-but-already-valid external component stylesheets are now still registered in the stylesheet registry, which keeps the Angular stylesheet HMR bookkeeping honest even when preprocessing makes no textual change

Verification:

  • CI=1 pnpm nx run tailwind-debug-app-e2e:e2e passed
  • pnpm vitest run --config packages/vite-plugin-angular/vite.config.ts packages/vite-plugin-angular/src/lib/angular-vite-plugin-live-reload.spec.ts passed
  • pnpm oxlint -c oxlint.config.ts --quiet ... on touched files: 0 errors

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Tip

For best results, initiate chat on the files or code changes.

@benpsnyder — noted. Here's what I'm tracking for this commit:

Confirmed changes in 9e0be352:

File Change
apps/tailwind-debug-app/vite.config.ts HMR wiretap now intercepts both server.ws.send and environment.hot.send; full-reload payloads include a stack trace
apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts Reframed from "CSS updates without reload" assertion to an upstream repro harness — now asserts stylesheet edit detected → full-reload via environment.hot.send → state resets → stack includes updateModules
packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts Unchanged-but-valid external stylesheets are now still registered in the registry, keeping HMR bookkeeping honest for no-op preprocesses

The pivot is well-reasoned: the test now serves as a documented repro signal for tailwindlabs/tailwindcss#19903 rather than asserting behavior that doesn't yet hold. The stack-capture addition in the wiretap is a good addition for providing actionable upstream data.

The angular-vite-plugin.ts fix for always-registering unchanged externals is a correctness improvement independent of the repro story — prevents the registry from going stale on no-op preprocess runs.

Outstanding tracking unchanged:

  • analog-app-e2e Playwright suite still needs a full run before merge.
  • ⏳ brandonroberts's "should fix before merge" items (tag-matching regex #1/[FIX]: Speed up Angular Vite Plugin #2, investigation file #10, breaking change label for devtools split) — still open unless addressed separately.

✏️ Learnings added
Learnt from: benpsnyder
URL: https://github.com/analogjs/analog/pull/2226

Timestamp: 2026-04-05T04:29:55.116Z
Learning: In `apps/tailwind-debug-app-e2e/tests/component-css-hmr.spec.ts` (PR `#2226`, commit 9e0be352), the E2E test was reframed from asserting "CSS updates without full reload" to serving as a repro harness for tailwindlabs/tailwindcss#19903. It now asserts: (1) stylesheet edit detected, (2) `environment.hot.send` emits `full-reload`, (3) app state resets after reload, (4) stack trace includes `updateModules`. Do not suggest reverting this to the previous assertion that CSS updates without reload — the test intentionally documents the current (broken) upstream behavior.

Learnt from: benpsnyder
URL: https://github.com/analogjs/analog/pull/2226

Timestamp: 2026-04-05T04:29:55.116Z
Learning: In `apps/tailwind-debug-app/vite.config.ts` (PR `#2226`, commit 9e0be352), the HMR wiretap plugin intercepts both `server.ws.send` AND `environment.hot.send`. Full-reload payloads capture a stack trace for upstream Tailwind issue reproduction fidelity. Do not suggest removing the `environment.hot.send` interception or the stack capture — they are intentional for providing actionable data for tailwindlabs/tailwindcss#19903.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-03-25T07:19:07.510Z
Learning: In `analogjs/analog`, the `e2e:analog` suite (`apps/analog-app-e2e` / `json-ld.spec.ts`) has a pre-existing failure introduced in commit `9ed63a6b` (typed routes + JSON-LD): the prerender step produces empty `<analogjs-root></analogjs-root>` because Angular SSR does not execute during Nitro's prerender phase, so JSON-LD scripts are never injected. This failure is present on the `alpha` branch and is not caused by the `feat/investigate-nitro-vite-plugin` migration (PR `#2188`).

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-04T19:18:17.239Z
Learning: In `packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`, `angularVitestPlugins()` helpers are NOT the HMR toggle. HMR is controlled by dedicated HMR helper plugins gated by `shouldEnableHmr()`. Tests for the HMR option (`hmr: false`, `liveReload: true`, etc.) must assert against the HMR helper plugins under a development-like env, not against the always-present Vitest plugins. See PR `#2226`, `packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts` line 24 for the corrected test pattern.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `packages/platform/src/lib/router-plugin.ts` (PR `#2226`, commit 779c9c8f4), `add` and `unlink` watcher events now trigger a full rescan of all surviving `.page.ts` files for route idiom diagnostics (not just the file that changed). This is intentional: removing or adding a file can affect layout detection for sibling routes. Do not suggest scoping the rescan back to just the changed file.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-04T19:18:17.239Z
Learning: In `packages/vite-plugin-angular/src/lib/stylesheet-registry.ts`, the basename alias for stylesheet registry lookups was intentionally removed because it is collision-prone. The module also rewrites `import url(...)` CSS import forms (not just bare `import "..."` forms) to absolute paths. Do not suggest restoring the basename alias or flag the url()-form rewrite as missing.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `packages/platform/src/lib/content-plugin.ts` (PR `#2226`), the `analog:debug-full-reload` WebSocket payload emits `normalizedPath` (not the raw watcher-provided path). Do not flag the normalized form as incorrect or suggest reverting to the raw path.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-05T04:18:48.602Z
Learning: In `apps/docs-app/docs/guides/migrating.md` (PR `#2226`), the `hmr`/`liveReload` HMR section now documents that Analog's Angular HMR requires Angular v19+. Angular v16–v18 will disable HMR at runtime and emit a console warning. Do not flag this warning as unnecessary — it is the designed diagnostic for the Angular version constraint, consistent with the warning in `angular-vite-plugin.ts`.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/router/src/lib/route-manifest.spec.ts:723-734
Timestamp: 2026-03-24T13:22:40.861Z
Learning: In `packages/router/src/lib/route-manifest.spec.ts` (and the corresponding `detectSchemaExports` implementation in `packages/router/manifest/src/index.ts`), the regex-based schema export detector intentionally false-positives on commented-out `export const routeParamsSchema`/`export const routeQuerySchema` lines. This is an accepted v1 limitation explicitly documented in the test. The test expectation `expect(result.hasParamsSchema).toBe(true)` for commented-out content is correct and intentional. Do not flag this as a bug or suggest changing the expectation to `false` — it will be addressed if/when schema detection is upgraded to AST-based parsing.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/platform/vite.config.lib.ts:0-0
Timestamp: 2026-03-22T23:12:26.945Z
Learning: In `tools/build/shared-plugins.ts` (and any package that uses it), the `oxcDtsPlugin` catch blocks around `isolatedDeclarationSync` calls were updated (PR `#2144`) to re-throw with file context on genuine failures. The normal "best-effort skip" path (file lacks explicit type annotations) is NOT an exception — `isolatedDeclarationSync` simply returns empty `code`, so the catch block change does not affect that case. Only the "already exists" probe catch around the secondary walk remains intentionally silent. Do not revert the catch blocks back to silent/empty form for the declaration-emission loops.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/vite-plugin-routes/src/lib/route-file-discovery.ts:53-83
Timestamp: 2026-03-24T13:23:00.699Z
Learning: In `packages/vite-plugin-routes/src/lib/route-file-discovery.ts`, `getDiscoveredFileKind` intentionally uses convention-based `includes()` checks (e.g. `normalized.includes('/src/app/pages/')`, `normalized.includes('/src/app/routes/')`, `normalized.includes('/src/content/')`) to classify standard/well-known dirs. This is acceptable because the dev watcher delivers absolute paths and those path segments are sufficiently specific. Configurable `additionalPagesDirs`/`additionalContentDirs` are already root-scoped via `isWithinDir()`. Do not flag the standard-dir `includes()` checks as a monorepo scope-leak risk — the design is intentional.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/content/vite.config.ts:24-26
Timestamp: 2026-03-24T13:22:20.254Z
Learning: In `packages/content/vite.config.ts`, the `resolve: { tsconfigPaths: true }` block is pre-existing dead config. `tsconfigPaths` is not a valid Vite `resolve` option and is silently ignored. The maintainer has explicitly decided not to fix this as it is unrelated to in-flight PRs. Do not flag this as an issue in future reviews of this file.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-04-04T19:18:17.239Z
Learning: In `packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`, when Angular <19 is detected, HMR is intentionally forced off and a visible `console.warn` is now emitted to surface this to developers in dev mode. This warning is not a bug — it is the designed diagnostic for the Angular version constraint. Do not flag the warn as unnecessary or suggest suppressing it.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2125
File: packages/create-analog/template-angular-v17/vite.config.ts:15-17
Timestamp: 2026-03-24T13:23:18.967Z
Learning: In `packages/create-analog/template-angular-v17/vite.config.ts` and `packages/create-analog/template-angular-v18/vite.config.ts`, the `__TAILWIND_PLUGIN__` placeholder is substituted by `create-analog/src/index.js` (around line 214) with either `'    tailwindcss(),\n'` (includes indentation, trailing comma, and newline) when Tailwind is selected, or `''` (empty string) when it is not. Because the trailing comma is part of the substitution value itself, the resulting `plugins` array is always valid JS after substitution. The raw template is intentionally not directly parseable as JS/TS before substitution. Do not report a syntax error for an apparent missing comma between `__TAILWIND_PLUGIN__` and `analog()` — the comma is supplied by the substitution value, not the template literal.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2144
File: packages/vite-plugin-angular-tools/vite.config.lib.ts:10-11
Timestamp: 2026-03-22T22:11:42.083Z
Learning: In `packages/vite-plugin-angular-tools/vite.config.lib.ts` (and other `vite.config.lib.ts` build configs in analogjs/analog), the import of `oxcDtsPlugin` from `../../tools/build/shared-plugins.ts` uses a relative cross-package path with an `// eslint-disable-next-line nx/enforce-module-boundaries` suppression. This is intentional and correct: `tools/build/shared-plugins.ts` is a build-time utility with no registered TypeScript path alias in `tsconfig.base.json`. Do not flag this relative import or its eslint suppression as a violation — suggesting a non-existent workspace alias would break the import.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 2226
File: packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts:1357-1359
Timestamp: 2026-04-04T19:28:54.429Z
Learning: In `packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`, the exported `normalizeIncludeGlob(workspaceRoot, glob)` helper normalizes `PluginOptions.include` globs using a three-way dispatch matching `normalizeWatchedDir` in `router-plugin.ts`: (1) already-rooted paths (starts with workspaceRoot) are returned unchanged; (2) `/`-prefixed workspace-relative paths are prepended with workspaceRoot; (3) bare relative globs are resolved via `resolve(workspaceRoot, glob)`. This was introduced in PR `#2226` (commit 20859121a01fdf01d18d8d87a4b4a921d3ecf67e). Do not flag the simple string-concatenation pattern as a bug — it no longer exists; `normalizeIncludeGlob` is used instead.

Learnt from: benpsnyder
Repo: analogjs/analog PR: 0
File: :0-0
Timestamp: 2026-03-25T07:19:07.510Z
Learning: In `packages/vite-plugin-nitro/src/lib/analog-vite-plugin.ts` (PR `#2188`), the `nitro/vite` plugin is restricted to serve mode only (i.e., not included during production builds). This is intentional to prevent conflicts with Nx executor output directory management during builds.

@benpsnyder benpsnyder changed the title fix: overhaul stylesheet pipeline and improve Tailwind HMR across Analog refactor: overhaul stylesheet pipeline and improve Tailwind HMR across Analog Apr 5, 2026
@benpsnyder
Copy link
Copy Markdown
Contributor Author

image

Copy link
Copy Markdown
Member

@brandonroberts brandonroberts left a comment

Choose a reason for hiding this comment

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

🚢

@brandonroberts brandonroberts merged commit 4200fd8 into analogjs:alpha Apr 5, 2026
23 checks passed
@brandonroberts brandonroberts deleted the fix/tailwind branch April 5, 2026 05:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:astro-angular Changes in @analogjs/astro-angular scope:ci GitHub workflow changes scope:content Changes in @analogjs/content scope:create-analog Changes in create-analog scope:docs Documentation changes scope:nx-plugin Changes in @analogjs/nx-plugin scope:platform Changes in @analogjs/platform scope:repo Repository metadata and tooling scope:router Changes in @analogjs/router scope:storybook-angular Changes in @analogjs/storybook-angular scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vite-plugin-nitro Changes in @analogjs/vite-plugin-nitro scope:vitest-angular Changes in @analogjs/vitest-angular

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants