Per ADR-0020 §"Shared-libs graduation": the three primitives that
both `portal-shell` (today) and `portal-admin` (next) will share
move out of `apps/portal-shell/src/` and into the workspace libs.
Mechanical move — no behaviour change.
Graduated:
- `Icon` component → `libs/shared/ui/src/lib/icon/`. Selector
renamed `app-icon` → `lib-icon` (consumed via `<lib-icon>`),
`IconName` re-exported from `shared-ui`.
- `LayoutStateService` (+ `ThemeMode` type) →
`libs/shared/state/src/lib/`. New library generated via
`nx g @nx/angular:library` to match the existing
`feature-auth` / `shared-ui` shape.
- Brand-palette `@theme` block →
`libs/shared/tokens/src/brand-tokens.css`. `portal-shell`'s
`styles.css` imports it by relative path.
Lib tags updated so the module-boundary lint rule lets both apps
consume them:
- `shared-state`: new lib → `scope:shared, type:shared`.
- `shared-ui`: was `scope:portal-shell` (stopgap from the initial
scaffold) → `scope:shared, type:shared`.
Side-edits:
- `tsconfig.base.json` gains the `shared-state` path alias
(generator-applied).
- `shared-tokens` is now CSS-only — its placeholder TS function is
removed and its vitest config flips `passWithNoTests: true` since
there is nothing to test there yet.
- Placeholder `shared-ui/shared-ui.{ts,html,css,spec.ts}` files
removed.
Verification:
- Lint / test / build run-many across the four projects — green.
- portal-shell: 28 tests, shared-state: 9, shared-ui: 3 (= the same
40 specs as before, redistributed across the new libs).
- Production build: 128.7 KB gzip initial per locale (was 128.5 KB
on main — noise-level delta).
- Brand tokens still inlined in the produced `styles-*.css`.
- Both `dist/.../browser/{en,fr}/` emitted as expected.
No public-API breakage. The migration to `portal-admin` is now a
clean `import { Icon } from 'shared-ui'` + `LayoutStateService`
inject + `@import` of `brand-tokens.css` on admin's `styles.css` —
no duplication.
Wire Tailwind CSS 4 in the portal-shell app per ADR-0016 (the future
host of spartan-ng components in libs/shared/ui will read from the
Tailwind tokens via the shared-tokens lib).
- pnpm add -D tailwindcss @tailwindcss/postcss postcss
- apps/portal-shell/postcss.config.js declares @tailwindcss/postcss
- apps/portal-shell/src/styles.scss renamed to styles.css and now
contains a single @import 'tailwindcss' directive. Plain CSS for the
global file avoids the Sass @import deprecation warning that fires
when Tailwind directives sit inside SCSS. Component-level styles can
still use SCSS.
- apps/portal-shell/project.json styles entry updated accordingly.
Side fix: align libs/shared/tokens and libs/shared/util tsconfigs from
module: commonjs to module: esnext. The Nx @nx/js:library --bundler=
tsc generator emits commonjs by default, but tsconfig.base.json
specifies moduleResolution: bundler, which TS only allows alongside
esnext or es2015+ modules. Without the alignment, those libs failed to
build (TS5095). All apps and libs now build green.
spartan-ng wiring is intentionally NOT in this commit. spartan-ng is
currently at 0.0.1-alpha.681 - clearly pre-1.0, which trips the
project rule against pre-1.0 dependencies. ADR-0016 chose spartan-ng
with the copy-paste mitigation, but the alpha state warrants an
explicit go/no-go decision before committing the workspace to it.
Generate the four phase-4 libraries:
- libs/shared/tokens (project name shared-tokens) - plain TS lib via
@nx/js:library; will host the a11y design tokens (palette,
contrast tiers, spacing, motion) once Tailwind lands in phase 5;
consumable by both apps; tagged scope:shared, type:shared.
- libs/shared/util (shared-util) - plain TS lib for cross-cutting
utility code; tagged scope:shared, type:shared.
- libs/shared/ui (shared-ui) - Angular standalone library that will
host the spartan-ng components copy-pasted in phase 5; Angular-only
so tagged scope:portal-shell, type:shared. unitTestRunner=
vitest-analog because vitest-angular requires a buildable lib.
- libs/feature/auth (feature-auth) - placeholder Angular standalone
feature lib to demonstrate the type:feature pattern; tagged
scope:portal-shell, type:feature.
@nx/enforce-module-boundaries depConstraints replaced (root
eslint.config.mjs) with the rules from ADR-0003:
scope:portal-shell -> scope:portal-shell, scope:shared
scope:portal-bff -> scope:portal-bff, scope:shared
scope:shared -> scope:shared
type:app -> type:feature, type:shared
type:feature -> type:feature, type:shared
type:shared -> type:shared
This forbids portal-shell from importing portal-bff code (and vice
versa) and prevents shared libs from depending on feature libs.
Project names follow the convention of ADR-0003 (feature-<name> /
shared-<scope>) by passing --name explicitly to the generator; the Nx
22 default takes only the last directory segment.
Sanity check: pnpm nx run-many -t lint and -t test pass for the 8
projects (4 apps/e2e + 4 libs).
Side effects from the generators: tsconfig.base.json paths populated
with the lib import aliases; nx.json gains vite/playwright plugin
entries; .gitignore picks up vitest.config.*.timestamp* (Vitest temp
files); package.json gains @analogjs/vitest-angular and related
devDeps; pnpm-lock.yaml regenerated. Two eslint-disable comments in
portal-bff-e2e support files were trimmed by lint --fix - those files
already lint clean without the directive.