feat(WP-67): merge behandelportal into this repo as a monorepo

Restructures into apps/ssp + apps/behandelportal (two Angular projects)
plus libs/shared + libs/beheer (cross-app libraries), replacing WP-61's
separate sibling repo. That split had already produced real drift: a
hand-vendored copy of the backend's OpenAPI doc, a shared/ui+layout tree
forked and silently diverging (7 files), and beheer + the styles.scss
token bridge duplicated byte-for-byte across both repos.

- git mv the SSP's src/app/* into apps/ssp/; fold shared/, beheer/,
  environments/, the Storybook docs/*.mdx, and styles.scss into
  libs/shared + libs/beheer (all confirmed identical between the two
  repos before merging). auth stays deliberately duplicated per
  ADR-0002 (actor-specific, expected to diverge) - amended there.
- One generated API client (libs/shared), no more vendored swagger.json.
- .dependency-cruiser split into a base factory + one config per app,
  and Storybook into .storybook-ssp/.storybook-behandelportal - both
  forced by the @auth/* alias resolving to different directories per app.
- SiteHeaderComponent/ShellComponent gained HEADER_NAV_ITEMS/
  HEADER_ADMIN_LINKS/DEBUG_PANEL injection tokens so each app supplies
  its own nav/admin-links/dev-panel instead of one being hardcoded.
- CLAUDE.md, ARCHITECTURE.md, dependencies.md, and ADR-0002 updated;
  WP-67 backlog entry documents the full decision trail.

npm run ci green (lint, dep:check x2, 360 tests across ssp/
behandelportal/shared/beheer, both localized builds, backend tests,
snippet + api-client drift); both dev servers, both Storybook
instances, and docker compose verified working.

The old sibling repo (/home/eho/repos/behandelportal) is left
untouched, not deleted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-08-02 21:01:57 +02:00
co-authored by Claude Sonnet 5
parent d3f3b13345
commit e7156c5132
403 changed files with 7103 additions and 60917 deletions
+39
View File
@@ -0,0 +1,39 @@
import type { StorybookConfig } from '@storybook/angular';
import remarkGfm from 'remark-gfm';
// WP-67: one config dir per app — a single merged tsconfig can't resolve both apps' @auth/*
// alias at once (each points at a different physical directory), so this Storybook instance
// only ever compiles under the ssp project's own browserTarget/tsconfig (see angular.json's
// ssp:storybook target) and only globs ssp's own stories + the shared libraries'.
const config: StorybookConfig = {
stories: [
'../apps/ssp/src/**/*.mdx',
'../libs/shared/**/*.mdx',
'../libs/beheer/**/*.mdx',
'../apps/ssp/src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
'../libs/shared/**/*.stories.@(js|jsx|mjs|ts|tsx)',
'../libs/beheer/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
addons: [
'@storybook/addon-a11y',
// remark-gfm so GFM pipe tables in *.mdx docs actually render (addon-docs
// doesn't parse them without it).
{
name: '@storybook/addon-docs',
options: { mdxPluginOptions: { mdxCompileOptions: { remarkPlugins: [remarkGfm] } } },
},
'@storybook/addon-onboarding',
],
framework: '@storybook/angular',
// Serve the vendored CIBG package so preview-head.html can <link> its CSS (and its
// relative font/icon/image url()s resolve) — mirrors index.html for the real app.
staticDirs: ['../public'],
// Webpack's own auto-DefinePlugin (from `optimization.nodeEnv`) and Storybook's preset both
// define `process.env.NODE_ENV` on the same compile, which webpack warns about as
// "Conflicting values" even though they resolve to the same string. Disable the redundant one.
webpackFinal: async (config) => {
config.optimization = { ...config.optimization, nodeEnv: false };
return config;
},
};
export default config;
+5
View File
@@ -0,0 +1,5 @@
<!-- CIBG Huisstijl (customized Bootstrap 5.2), served from the vendored public/ staticDir.
Mirrors src/index.html so stories match the app. System font per styles.scss. -->
<link rel="stylesheet" href="cibg-huisstijl/css/huisstijl.min.css" />
<!-- Letter-rendering contract (WP-24), mirrors index.html. -->
<link rel="stylesheet" href="letter.css" />
+59
View File
@@ -0,0 +1,59 @@
import type { Preview } from '@storybook/angular';
import { componentWrapperDecorator } from '@storybook/angular';
import { setCompodocJson } from '@storybook/addon-docs/angular';
import docJson from './documentation.json';
setCompodocJson(docJson);
// Activate CIBG's official palette in the story iframe. The override is `body.brand--cibg`,
// so it must sit on <body> (a wrapper <div> won't match) — mirrors index.html.
if (typeof document !== 'undefined') document.body.classList.add('brand--cibg');
const preview: Preview = {
// CIBG/Bootstrap styles bare elements — no theme wrapper class needed; just pad.
// The token bridge lives in styles.scss (loaded via the app build target).
decorators: [componentWrapperDecorator((story) => `<div style="padding:1.5rem">${story}</div>`)],
parameters: {
layout: 'padded',
// Accessibility (axe) via @storybook/addon-a11y. Runs WCAG 2.0/2.1 A+AA rule
// sets on every story; flag violations in the a11y panel.
a11y: {
config: { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] } },
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
// Sidebar tells the DDD seam: reusable design system, then domain contexts.
// See libs/shared/docs/layers.mdx.
options: {
storySort: {
order: [
'Foundations',
[
'Overview',
'Learning Path',
'Domain-Driven Design',
'Atomic Design',
'FP in the UI',
'State Machines (TEA)',
'RemoteData & Async',
"Parse, don't validate",
'Design Tokens',
'CIBG Gap Register',
'Accessibility',
'Testing strategy',
'BDD',
'Internationalization',
],
'Design System',
['Atoms', 'Molecules', 'Organisms', 'Templates', 'Devtools'],
'Domein',
],
},
},
},
};
export default preview;
+21
View File
@@ -0,0 +1,21 @@
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
import { injectAxe, checkA11y } from 'axe-playwright';
const config: TestRunnerConfig = {
async preVisit(page) {
await injectAxe(page);
},
async postVisit(page, context) {
const storyContext = await getStoryContext(page, context);
if (storyContext.parameters?.a11y?.disable) return;
await checkA11y(page, '#storybook-root', {
axeOptions: storyContext.parameters?.a11y?.config,
detailedReport: true,
detailedReportOptions: { html: true },
});
},
};
export default config;
+18
View File
@@ -0,0 +1,18 @@
// This tsconfig is used by Compodoc to generate the documentation for the project.
// If Compodoc is not used, this file can be deleted.
{
"extends": "./tsconfig.json",
// Exclude all files that are not needed for documentation generation.
"exclude": [
"../apps/ssp/src/test.ts",
"../apps/ssp/src/**/*.spec.ts",
"../apps/ssp/src/**/*.stories.ts",
"../libs/shared/**/*.spec.ts",
"../libs/shared/**/*.stories.ts",
"../libs/beheer/**/*.spec.ts",
"../libs/beheer/**/*.stories.ts"
],
// Please make sure to include all files from which Compodoc should generate documentation.
"include": ["../apps/ssp/src/**/*", "../libs/shared/**/*", "../libs/beheer/**/*"],
"files": ["./typings.d.ts"]
}
+17
View File
@@ -0,0 +1,17 @@
{
"extends": "../apps/ssp/tsconfig.app.json",
"compilerOptions": {
"types": ["node", "@angular/localize"],
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"moduleResolution": "bundler"
},
"exclude": ["../apps/ssp/src/test.ts", "../apps/ssp/src/**/*.spec.ts"],
"include": [
"../apps/ssp/src/**/*.stories.*",
"../libs/shared/**/*.stories.*",
"../libs/beheer/**/*.stories.*",
"./preview.ts"
],
"files": ["./typings.d.ts"]
}
+4
View File
@@ -0,0 +1,4 @@
declare module '*.md' {
const content: string;
export default content;
}