Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
51d68a7
feat: migrate ESLint/Prettier to Oxlint/Oxfmt via vite-plus (closes #…
chrisjwalk-bot May 10, 2026
16df968
feat: use vp run for test/lint/fmt instead of nx
chrisjwalk-bot May 10, 2026
2ce5f7a
chore: remove @nx/vite, update CI to use vp run and vp lint
chrisjwalk-bot May 10, 2026
275a406
fix: make vp check pass cleanly
chrisjwalk-bot May 10, 2026
d18aa10
fix: configure vp test with Vitest projects for monorepo
chrisjwalk-bot May 10, 2026
f07abff
fix: use lazyPlugins and plain object export in root vite.config.ts
chrisjwalk-bot May 10, 2026
2b4dbf6
fix: replace stale nx scripts in package.json, restore start with con…
chrisjwalk-bot May 10, 2026
9f8f40e
fix: remove nx from playwright e2e config, update e2e scripts
chrisjwalk-bot May 10, 2026
f4b644f
chore: remove all nx packages and config files
chrisjwalk-bot May 10, 2026
8c46218
chore: remove unused packages and fix update-packages tool
chrisjwalk-bot May 10, 2026
4ae1589
fix: run dotnet tests with "vp test"
chrisjwalk-bot May 16, 2026
02b75f2
fix: remove hardcoded pnpm version from CI workflows
chrisjwalk-bot May 16, 2026
9c79e73
fix: approve pnpm build scripts for native deps
chrisjwalk-bot May 16, 2026
737a120
fix: remove broken allowBuilds section from pnpm-workspace.yaml
chrisjwalk-bot May 16, 2026
dd6fc67
fix: replace onlyBuiltDependencies with allowBuilds for pnpm v11
chrisjwalk-bot May 16, 2026
c3f4415
fix(ci): remove nx-dependent build/pack steps after vite+ migration
chrisjwalk-bot May 16, 2026
696814e
fix(ci): fix preview build after vite+ migration
chrisjwalk-bot May 16, 2026
819014d
docs: decouple home page from README, update for vite+ migration
chrisjwalk-bot May 17, 2026
8759123
feat: merge Vite+ migration with Angular 22 MFE setup
chrisjwalk-bot Jun 8, 2026
59773e1
fix: resolve counter-remote MFE stub in test mode
chrisjwalk-bot Jun 8, 2026
731890a
chore: remove deprecated pnpm.overrides from package.json
chrisjwalk-bot Jun 8, 2026
37bfb57
fix(ci): remove conflicting pnpm version from CI workflow
chrisjwalk-bot Jun 8, 2026
2b59795
fix: e2e tests, dev server, and lint-staged
chrisjwalk-bot Jun 8, 2026
9a09e47
fix: resolve E2E federation import and migrate to vp staged (closes #…
chrisjwalk-bot Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 146 additions & 30 deletions .claude/skills/create-mfe/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ to spread the feature routes and add a wildcard fallback:
import { Route } from '@angular/router';
import { myFeatureRoutes } from '@myorg/my-feature';

export const routes: Route[] = [...myFeatureRoutes, { path: '**', redirectTo: '' }];
export const routes: Route[] = [
...myFeatureRoutes,
{ path: '**', redirectTo: '' },
];
```

Delete generated pages/routes you don't need — the remote exposes its feature
Expand All @@ -75,7 +78,8 @@ export const myFeatureRoutes: Route[] = [
{
path: '',
title: 'My Feature',
loadComponent: () => import('./my-feature/my-feature').then((m) => m.MyFeature),
loadComponent: () =>
import('./my-feature/my-feature').then((m) => m.MyFeature),
providers: [MyFeatureStore],
},
];
Expand Down Expand Up @@ -196,38 +200,144 @@ const sharedDeps = {
'@angular/core': { singleton: true, requiredVersion: angVer },
'@angular/forms': { singleton: true, requiredVersion: angVer },
'@angular/platform-browser': { singleton: true, requiredVersion: angVer },
'@angular/platform-browser/animations': { singleton: true, requiredVersion: angVer },
'@angular/platform-browser-dynamic': { singleton: true, requiredVersion: angVer },
'@angular/platform-browser/animations': {
singleton: true,
requiredVersion: angVer,
},
'@angular/platform-browser-dynamic': {
singleton: true,
requiredVersion: angVer,
},
'@angular/router': { singleton: true, requiredVersion: angVer },

// CDK sub-paths — import:false prevents NG0912 (see note above)
'@angular/cdk/a11y': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/bidi': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/layout': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/observers': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/overlay': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/portal': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/scrolling': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/text-field': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/cdk/a11y': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/bidi': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/layout': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/observers': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/overlay': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/portal': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/scrolling': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/cdk/text-field': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},

// Material sub-paths — import:false for same reason
'@angular/material/badge': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/bottom-sheet': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/button': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/checkbox': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/core': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/divider': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/form-field': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/icon': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/input': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/list': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/paginator': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/progress-spinner': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/sidenav': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/snack-bar': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/table': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/toolbar': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/tooltip': { singleton: true, requiredVersion: cdkMatVer, import: false },
'@angular/material/badge': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/bottom-sheet': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/button': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/checkbox': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/core': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/divider': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/form-field': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/icon': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/input': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/list': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/paginator': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/progress-spinner': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/sidenav': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/snack-bar': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/table': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/toolbar': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},
'@angular/material/tooltip': {
singleton: true,
requiredVersion: cdkMatVer,
import: false,
},

// NgRx + utilities
'@ngrx/signals': { singleton: true, requiredVersion: '~21.1.0' },
Expand Down Expand Up @@ -436,7 +546,13 @@ separate entries per remote:
{
"navigationFallback": {
"rewrite": "/index.html",
"exclude": ["/*.{css,js,png,gif,ico,jpg,svg,webmanifest,woff,woff2,txt}", "/counter-remote/*", "/counter-remote/assets/*", "/my-remote/*", "/my-remote/assets/*"]
"exclude": [
"/*.{css,js,png,gif,ico,jpg,svg,webmanifest,woff,woff2,txt}",
"/counter-remote/*",
"/counter-remote/assets/*",
"/my-remote/*",
"/my-remote/assets/*"
]
}
}
```
Expand Down
83 changes: 70 additions & 13 deletions .claude/skills/create-signalstore/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ Place the resource in `withProps` so it's accessible to downstream `withMethods`
```typescript
import { computed, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { patchState, signalStore, signalStoreFeature, withComputed, withHooks, withMethods, withProps, withState } from '@ngrx/signals';
import {
patchState,
signalStore,
signalStoreFeature,
withComputed,
withHooks,
withMethods,
withProps,
withState,
} from '@ngrx/signals';

export type MyState = { query: string };
export const myInitialState: MyState = { query: '' };
Expand Down Expand Up @@ -84,7 +93,10 @@ export function withMyFeature() {
);
}

export const MyStore = signalStore(withMyFeature(), withHooks({ onInit: ({ setQuery }) => setQuery('') }));
export const MyStore = signalStore(
withMyFeature(),
withHooks({ onInit: ({ setQuery }) => setQuery('') }),
);
```

**Using the resource in a template:**
Expand Down Expand Up @@ -138,7 +150,9 @@ it('should load results', async () => {

it('should capture error', async () => {
const appRef = TestBed.inject(ApplicationRef);
vi.spyOn(service, 'search').mockReturnValue(throwError(() => new Error('oops')));
vi.spyOn(service, 'search').mockReturnValue(
throwError(() => new Error('oops')),
);

store.setQuery('hello');
await appRef.whenStable();
Expand All @@ -156,7 +170,17 @@ Use when you have plain state + computed values + methods. No entity collection.

```typescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, signalStoreFeature, withComputed, withHooks, withMethods, withProps, withState, type } from '@ngrx/signals';
import {
patchState,
signalStore,
signalStoreFeature,
withComputed,
withHooks,
withMethods,
withProps,
withState,
type,
} from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { tapResponse } from '@ngrx/operators';
import { pipe, switchMap, tap } from 'rxjs';
Expand Down Expand Up @@ -241,7 +265,8 @@ withLinkedState(({ options }) => ({
// Advanced — preserve selection if it still exists in the new list:
selectedOption: linkedSignal<Option[], Option>({
source: options,
computation: (newOptions, previous) => newOptions.find((o) => o.id === previous?.value.id) ?? newOptions[0],
computation: (newOptions, previous) =>
newOptions.find((o) => o.id === previous?.value.id) ?? newOptions[0],
}),
}));
```
Expand All @@ -254,7 +279,17 @@ Use when managing a collection of items. Provides `entityMap`, `ids`, and `entit

```typescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, signalStoreFeature, withComputed, withHooks, withMethods, withProps, withState, type } from '@ngrx/signals';
import {
patchState,
signalStore,
signalStoreFeature,
withComputed,
withHooks,
withMethods,
withProps,
withState,
type,
} from '@ngrx/signals';
import {
addEntity,
addEntities,
Expand Down Expand Up @@ -303,7 +338,8 @@ export function withTodosFeature() {
switchMap(() =>
todosService.getAll().pipe(
tapResponse({
next: (todos) => patchState(store, setAllEntities(todos), { loading: false }),
next: (todos) =>
patchState(store, setAllEntities(todos), { loading: false }),
error: (error) => patchState(store, { error, loading: false }),
}),
),
Expand All @@ -323,7 +359,10 @@ export function withTodosFeature() {
),
),
toggle(id: string) {
patchState(store, updateEntity({ id, changes: (t) => ({ completed: !t.completed }) }));
patchState(
store,
updateEntity({ id, changes: (t) => ({ completed: !t.completed }) }),
);
},
remove(id: string) {
patchState(store, removeEntity(id));
Expand All @@ -332,7 +371,10 @@ export function withTodosFeature() {
);
}

export const TodosStore = signalStore(withTodosFeature(), withHooks({ onInit: ({ loadAll }) => loadAll() }));
export const TodosStore = signalStore(
withTodosFeature(),
withHooks({ onInit: ({ loadAll }) => loadAll() }),
);

export type TodosStore = InstanceType<typeof TodosStore>;
```
Expand All @@ -344,7 +386,10 @@ If your entity's ID field isn't named `id`:
```typescript
withEntities<WeatherForecast>();
// then pass selectId to entity updaters:
patchState(store, setAllEntities(items, { selectId: (item) => item.dateFormatted }));
patchState(
store,
setAllEntities(items, { selectId: (item) => item.dateFormatted }),
);

// Or use entityConfig to define it once:
import { entityConfig } from '@ngrx/signals/entities';
Expand Down Expand Up @@ -388,7 +433,14 @@ withEntities({ entity: type<Post>(), collection: 'posts' });
Use when you want explicit Redux-style events with a reducer (e.g. a counter, form steps, wizard).

```typescript
import { patchState, signalStore, signalStoreFeature, withMethods, withState, type } from '@ngrx/signals';
import {
patchState,
signalStore,
signalStoreFeature,
withMethods,
withState,
type,
} from '@ngrx/signals';
import { signalMethod } from '@ngrx/signals';
import { eventGroup, on, withReducer } from '@ngrx/signals/events';

Expand Down Expand Up @@ -427,7 +479,10 @@ export function withCounterReducer() {
);
}

export const CounterStore = signalStore(withCounterFeature(), withCounterReducer());
export const CounterStore = signalStore(
withCounterFeature(),
withCounterReducer(),
);

export type CounterStore = InstanceType<typeof CounterStore>;
```
Expand Down Expand Up @@ -479,7 +534,9 @@ describe('MyStore', () => {
});

it('should handle an event via reducer', () => {
const dispatch = TestBed.runInInjectionContext(() => injectDispatch(myEvents));
const dispatch = TestBed.runInInjectionContext(() =>
injectDispatch(myEvents),
);
dispatch.someEvent(42);
TestBed.flushEffects();
expect(store.someValue()).toBe(42);
Expand Down
Loading
Loading