Merge pull request #1: WS-3 sessie 1c: enable eslint-plugin-boundaries in apps/app/
This commit is contained in:
18
CLAUDE.md
18
CLAUDE.md
@@ -268,6 +268,24 @@ you are using available components rather than building custom ones.
|
||||
- Never: TypeScript `any` type (use proper types, generics, or `unknown` with type guards)
|
||||
- Never: import axios directly in a component (use `src/lib/axios.ts` via a composable)
|
||||
|
||||
## Frontend import boundaries (apps/app/)
|
||||
|
||||
`apps/app/` enforces a layered import architecture via
|
||||
`eslint-plugin-boundaries`. Ten zones (`types` → `utils` → `lib` →
|
||||
`plugins` / `composables` / `stores` / `navigation` → `components` →
|
||||
`layouts` → `pages`); each zone may only import from the zones below
|
||||
it in the matrix. Vendored `@core/` and `@layouts/` are exempt.
|
||||
Cross-zone violations are lint errors, not warnings.
|
||||
|
||||
Matrix details + rationale: `dev-docs/WS-3-SESSION-1C-AUDIT.md`.
|
||||
Config: `apps/app/.eslintrc.cjs`.
|
||||
|
||||
When adding a new file: pick the zone first. If your file imports
|
||||
from a zone the matrix forbids, the structural answer is usually to
|
||||
hoist a type to `types/` or extract a helper to `utils/` /
|
||||
`composables/` — not to disable the rule. Per-line disables are
|
||||
allowed only with a `TODO TECH-*` reference to a backlog item.
|
||||
|
||||
## Order of work for each new module
|
||||
|
||||
1. Create and run migration(s)
|
||||
|
||||
@@ -32,6 +32,7 @@ module.exports = {
|
||||
'@typescript-eslint',
|
||||
'regex',
|
||||
'regexp',
|
||||
'boundaries',
|
||||
],
|
||||
ignorePatterns: [
|
||||
'src/plugins/iconify/*.js',
|
||||
@@ -200,12 +201,68 @@ module.exports = {
|
||||
],
|
||||
'\\.eslintrc\\.cjs',
|
||||
],
|
||||
|
||||
// Architectural import boundaries (WS-3 1c, audit:
|
||||
// dev-docs/WS-3-SESSION-1C-AUDIT.md). The matrix is layered:
|
||||
// types → utils → lib → composables → stores → components → layouts → pages.
|
||||
// The `lib → stores` edge is intentionally disallowed; lib/axios.ts
|
||||
// uses dynamic `await import('@/stores/...')` for its 4 store reads
|
||||
// so the static-import surface stays clean.
|
||||
// Sub-zone enforcement (components/{organizer,portal,shared}) is a
|
||||
// backlog item (TECH-WS3-BOUNDARIES-SUBZONES); it lands after the
|
||||
// §4.2 consolidation directory layout.
|
||||
// FORWARD-FLAG: when src/plugins/1.router/ migrates to src/router/
|
||||
// in a later WS-3 PR, add `{ type: 'router', pattern: 'src/router/**' }`
|
||||
// to boundaries/elements and `{ from: 'router', allow: ['types',
|
||||
// 'utils', 'lib', 'plugins', 'stores'] }` to the rules.
|
||||
'boundaries/element-types': ['error', {
|
||||
default: 'disallow',
|
||||
rules: [
|
||||
{ from: 'types', allow: ['types'] },
|
||||
{ from: 'utils', allow: ['types', 'utils'] },
|
||||
{ from: 'lib', allow: ['types', 'utils', 'lib'] },
|
||||
{ from: 'plugins', allow: ['types', 'utils', 'lib', 'plugins', 'stores'] },
|
||||
{ from: 'composables', allow: ['types', 'utils', 'lib', 'composables', 'stores'] },
|
||||
{ from: 'stores', allow: ['types', 'utils', 'lib', 'composables', 'stores'] },
|
||||
{ from: 'navigation', allow: ['types', 'utils', 'navigation'] },
|
||||
{ from: 'components', allow: ['types', 'utils', 'lib', 'composables', 'stores', 'components'] },
|
||||
{ from: 'layouts', allow: ['types', 'utils', 'lib', 'composables', 'stores', 'navigation', 'components', 'layouts'] },
|
||||
{ from: 'pages', allow: ['types', 'utils', 'lib', 'composables', 'stores', 'navigation', 'components', 'layouts'] },
|
||||
],
|
||||
}],
|
||||
'boundaries/no-unknown': 'off', // External packages are fine.
|
||||
'boundaries/no-unknown-files': 'off', // The ignore list handles vendor/generated.
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: true,
|
||||
typescript: {},
|
||||
},
|
||||
|
||||
// Element-type assignment: first-match-wins. Order matters.
|
||||
'boundaries/elements': [
|
||||
{ type: 'types', pattern: 'src/types/**' },
|
||||
{ type: 'utils', pattern: 'src/utils/**' },
|
||||
{ type: 'lib', pattern: 'src/lib/**' },
|
||||
{ type: 'plugins', pattern: 'src/plugins/**' },
|
||||
{ type: 'composables', pattern: 'src/composables/**' },
|
||||
{ type: 'stores', pattern: 'src/stores/**' },
|
||||
{ type: 'navigation', pattern: 'src/navigation/**' },
|
||||
{ type: 'components', pattern: 'src/components/**' },
|
||||
{ type: 'layouts', pattern: 'src/layouts/**' },
|
||||
{ type: 'pages', pattern: 'src/pages/**' },
|
||||
],
|
||||
'boundaries/ignore': [
|
||||
'src/@core/**', // vendored Vuexy
|
||||
'src/@layouts/**', // vendored Vuexy
|
||||
'src/views/**', // single dead Vuexy file (zero importers); see TECH-DELETE-DEAD-VIEWS
|
||||
'src/App.vue', // orchestration root
|
||||
'src/main.ts', // orchestration root
|
||||
'src/assets/**', // static media
|
||||
'src/styles/**', // SCSS
|
||||
'**/*.d.ts', // generated declarations
|
||||
],
|
||||
'boundaries/include': ['src/**/*.{ts,vue,tsx}'],
|
||||
},
|
||||
overrides: [
|
||||
// Vue SFCs: the base lines-around-comment rule conflicts with
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-import-resolver-typescript": "3.10.1",
|
||||
"eslint-plugin-antfu": "0.43.1",
|
||||
"eslint-plugin-boundaries": "6.0.2",
|
||||
"eslint-plugin-case-police": "0.6.1",
|
||||
"eslint-plugin-es-x": "7.8.0",
|
||||
"eslint-plugin-eslint-comments": "3.2.0",
|
||||
|
||||
90
apps/app/pnpm-lock.yaml
generated
90
apps/app/pnpm-lock.yaml
generated
@@ -274,6 +274,9 @@ importers:
|
||||
eslint-plugin-antfu:
|
||||
specifier: 0.43.1
|
||||
version: 0.43.1(eslint@8.57.1)(typescript@5.9.3)
|
||||
eslint-plugin-boundaries:
|
||||
specifier: 6.0.2
|
||||
version: 6.0.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
eslint-plugin-case-police:
|
||||
specifier: 0.6.1
|
||||
version: 0.6.1(eslint@8.57.1)(typescript@5.9.3)
|
||||
@@ -581,6 +584,10 @@ packages:
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@boundaries/elements@2.0.1':
|
||||
resolution: {integrity: sha512-sAWO3D8PFP6pBXdxxW93SQi/KQqqhE2AAHo3AgWfdtJXwO6bfK6/wUN81XnOZk0qRC6vHzUEKhjwVD9dtDWvxg==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
||||
|
||||
@@ -2747,6 +2754,12 @@ packages:
|
||||
eslint-plugin-antfu@0.43.1:
|
||||
resolution: {integrity: sha512-Nak+Qpy5qEK10dCXtVaabPTUmLBPLhsVKAFXAtxYGYRlY/SuuZUBhW2YIsLsixNROiICGuov8sN+eNOCC7Wb5g==}
|
||||
|
||||
eslint-plugin-boundaries@6.0.2:
|
||||
resolution: {integrity: sha512-wSHgiYeMEbziP91lH0UQ9oslgF2djG1x+LV9z/qO19ggMKZaCB8pKIGePHAY91eLF4EAgpsxQk8MRSFGRPfPzw==}
|
||||
engines: {node: '>=18.18'}
|
||||
peerDependencies:
|
||||
eslint: '>=6.0.0'
|
||||
|
||||
eslint-plugin-case-police@0.6.1:
|
||||
resolution: {integrity: sha512-SNyZBjc39CwoNSOl3aiu5EsuHYXOIaPDraMsgLQmcH8CbEhllyOMkmV9kuSNHjjtM8iYRHsiBs1WEFw3/9qkoQ==}
|
||||
|
||||
@@ -3184,6 +3197,11 @@ packages:
|
||||
grid-index@1.1.0:
|
||||
resolution: {integrity: sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==}
|
||||
|
||||
handlebars@4.7.9:
|
||||
resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
|
||||
engines: {node: '>=0.4.7'}
|
||||
hasBin: true
|
||||
|
||||
happy-dom@20.9.0:
|
||||
resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
@@ -3793,6 +3811,9 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
|
||||
node-exports-info@1.6.0:
|
||||
resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4281,11 +4302,6 @@ packages:
|
||||
resolve-protobuf-schema@2.1.0:
|
||||
resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==}
|
||||
|
||||
resolve@1.22.11:
|
||||
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
resolve@1.22.12:
|
||||
resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4823,6 +4839,11 @@ packages:
|
||||
ufo@1.6.1:
|
||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||
|
||||
uglify-js@3.19.3:
|
||||
resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
hasBin: true
|
||||
|
||||
unbox-primitive@1.1.0:
|
||||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5240,6 +5261,9 @@ packages:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
wordwrap@1.0.0:
|
||||
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -5631,6 +5655,20 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@boundaries/elements@2.0.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)':
|
||||
dependencies:
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
handlebars: 4.7.9
|
||||
is-core-module: 2.16.1
|
||||
micromatch: 4.0.8
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
dependencies:
|
||||
cookie: 0.7.2
|
||||
@@ -7917,7 +7955,7 @@ snapshots:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.16.1
|
||||
resolve: 1.22.11
|
||||
resolve: 1.22.12
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -7983,6 +8021,21 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
eslint-plugin-boundaries@6.0.2(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@boundaries/elements': 2.0.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
chalk: 4.1.2
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
handlebars: 4.7.9
|
||||
micromatch: 4.0.8
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-case-police@0.6.1(eslint@8.57.1)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.3)
|
||||
@@ -8606,6 +8659,15 @@ snapshots:
|
||||
|
||||
grid-index@1.1.0: {}
|
||||
|
||||
handlebars@4.7.9:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
neo-async: 2.6.2
|
||||
source-map: 0.6.1
|
||||
wordwrap: 1.0.0
|
||||
optionalDependencies:
|
||||
uglify-js: 3.19.3
|
||||
|
||||
happy-dom@20.9.0:
|
||||
dependencies:
|
||||
'@types/node': 24.9.2
|
||||
@@ -9222,6 +9284,8 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
node-exports-info@1.6.0:
|
||||
dependencies:
|
||||
array.prototype.flatmap: 1.3.3
|
||||
@@ -9758,12 +9822,6 @@ snapshots:
|
||||
dependencies:
|
||||
protocol-buffers-schema: 3.6.0
|
||||
|
||||
resolve@1.22.11:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.12:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -9975,8 +10033,7 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map@0.6.1:
|
||||
optional: true
|
||||
source-map@0.6.1: {}
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
@@ -10394,6 +10451,9 @@ snapshots:
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
uglify-js@3.19.3:
|
||||
optional: true
|
||||
|
||||
unbox-primitive@1.1.0:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -10885,6 +10945,8 @@ snapshots:
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wordwrap@1.0.0: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import axios from 'axios'
|
||||
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
|
||||
// eslint-disable-next-line boundaries/element-types -- TECH-AXIOS-STORE-COUPLING: deliberate HTTP↔state seam, refactor scheduled per backlog.
|
||||
import { useNotificationStore } from '@/stores/useNotificationStore'
|
||||
// eslint-disable-next-line boundaries/element-types -- TECH-AXIOS-STORE-COUPLING: deliberate HTTP↔state seam, refactor scheduled per backlog.
|
||||
import { useOrganisationStore } from '@/stores/useOrganisationStore'
|
||||
|
||||
const apiClient: AxiosInstance = axios.create({
|
||||
@@ -58,6 +60,7 @@ apiClient.interceptors.response.use(
|
||||
|
||||
// Handle impersonation session expiry
|
||||
if (status === 403 && error.response?.data?.impersonation_ended) {
|
||||
// eslint-disable-next-line boundaries/element-types -- TECH-AXIOS-STORE-COUPLING: deliberate HTTP↔state seam, refactor scheduled per backlog.
|
||||
const { useImpersonationStore } = await import('@/stores/useImpersonationStore')
|
||||
const impersonationStore = useImpersonationStore()
|
||||
|
||||
@@ -69,6 +72,7 @@ apiClient.interceptors.response.use(
|
||||
|
||||
if (status === 401) {
|
||||
// Lazy import to avoid circular dependency
|
||||
// eslint-disable-next-line boundaries/element-types -- TECH-AXIOS-STORE-COUPLING: deliberate HTTP↔state seam, refactor scheduled per backlog.
|
||||
const { useAuthStore } = await import('@/stores/useAuthStore')
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.isInitialized)
|
||||
|
||||
@@ -551,6 +551,30 @@ Zie §4 voor scope en stappen.
|
||||
drie geplande wijzigingen, doorschuiven naar follow-up. WS-3 lint
|
||||
cleanup workstream effectief afgerond; sessie 1c
|
||||
(eslint-plugin-boundaries) kan starten op een schone baseline.
|
||||
- **Sessie 1c (2026-04-30)** — _import-boundaries enforcement, klaar._
|
||||
`eslint-plugin-boundaries@6.0.2` (MIT, ESLint ≥6 peer-dep, Node ≥18.18)
|
||||
toegevoegd als directe devDep aan `apps/app/package.json` per de
|
||||
TECH-PORTAL-ESLINT-DEPS lesson, en geactiveerd in `apps/app/.eslintrc.cjs`
|
||||
met layered-architecture matrix: 10 zones (`types`, `utils`, `lib`,
|
||||
`plugins`, `composables`, `stores`, `navigation`, `components`, `layouts`,
|
||||
`pages`) met richtgevende edges (rationale + bewijs in
|
||||
`dev-docs/WS-3-SESSION-1C-AUDIT.md`). Vendored `@core/`, `@layouts/`,
|
||||
het dode `views/` bestand en orchestratie-roots `App.vue` + `main.ts`
|
||||
staan in `boundaries/ignore`. Vier `lib → stores` violations in
|
||||
`lib/axios.ts` (regels 3, 4, 61, 72) gemarkeerd met per-line
|
||||
`eslint-disable-next-line` comments referencerend naar
|
||||
`TECH-AXIOS-STORE-COUPLING` — de structurele decoupling van axios is
|
||||
bewust uitgesteld naar een dedicated sessie omdat het architectuurwerk
|
||||
is, geen tooling-cleanup. Boundary-rule blijft `error`, matrix blijft
|
||||
strict; toekomstige `lib/X.ts` schrijvers stoten alsnog tegen de regel.
|
||||
Lint baseline 0 errors / 0 warnings; build smoke groen; Vitest groen
|
||||
(49 tests). Drie backlog-items aangemaakt voor toekomstige actie:
|
||||
`TECH-AXIOS-STORE-COUPLING` (decouple axios), `TECH-DELETE-DEAD-VIEWS`
|
||||
(verwijder `src/views/`), `TECH-WS3-BOUNDARIES-SUBZONES` (sub-zone
|
||||
enforcement na PR-B), `TECH-WS3-BOUNDARIES-ROUTER-ZONE` (matrix-update
|
||||
wanneer `plugins/1.router/` naar `router/` verhuist). WS-3 lint cleanup
|
||||
+ boundaries enforcement effectief afgerond; volgende WS-3 stap is PR-B
|
||||
(portal merge) zodra WS-6 sessie 2 in main is geland.
|
||||
|
||||
**Klaar-criteria:**
|
||||
- `apps/portal/` is verwijderd
|
||||
|
||||
@@ -622,6 +622,139 @@ TECH-ESLINT-V9-MIGRATION zijn natuurlijke kandidaten).
|
||||
|
||||
---
|
||||
|
||||
### TECH-AXIOS-STORE-COUPLING — Decouple lib/axios.ts from stores layer
|
||||
|
||||
**Aanleiding:** WS-3 sessie 1c (eslint-plugin-boundaries enforcement)
|
||||
constateerde dat `apps/app/src/lib/axios.ts` 4 imports heeft uit `stores/`
|
||||
(2 statisch op regel 3-4 voor `useNotificationStore` /
|
||||
`useOrganisationStore`, 2 dynamisch op regel 61, 72 voor
|
||||
`useImpersonationStore` / `useAuthStore` uit 1b-iii). De `lib → stores`
|
||||
edge schendt de layered-architecture matrix. Om sessie 1c on-time te
|
||||
landen zijn de 4 sites gemarkeerd met `eslint-disable-next-line` +
|
||||
verwijzing naar dit backlog-item; de structurele fix is bewust uitgesteld
|
||||
naar een dedicated sessie omdat het architectuurwerk is, geen tooling-
|
||||
cleanup.
|
||||
|
||||
**Wat:**
|
||||
- Decouple `lib/axios.ts` van stores zodat het puur HTTP-infrastructuur
|
||||
wordt. Twee paden, kies bij refactor:
|
||||
- **Approach 1 (preferred starting point):** `lib/axios.ts` exporteert
|
||||
de axios-instance plus een `registerInterceptors(client, deps)`
|
||||
functie die callbacks accepteert (`onAuthFail`,
|
||||
`onImpersonationDrop`, `getActiveOrgId`, `notify(message, level)`).
|
||||
Een nieuwe `plugins/axios-bindings.ts` (mag `stores` importeren per
|
||||
matrix) roept `registerInterceptors` aan bij app-init met closures
|
||||
over de stores.
|
||||
- **Approach 2 (fallback):** event-bus / callback registry; axios.ts
|
||||
emit-eert semantische events (`auth-failed`, `needs-org-header`,
|
||||
`notify-error`) en `plugins/axios-bindings.ts` subscribet.
|
||||
- Verwijder alle 4 `eslint-disable-next-line` comments uit
|
||||
`lib/axios.ts`.
|
||||
- Tests moeten dekken: X-Organisation-Id header injection, 401/403
|
||||
logout flow, impersonation revocation flow, error toast op 4xx/5xx.
|
||||
- Optioneel: meteen ook de static/dynamic import-split in axios.ts
|
||||
uniformeren (nu inconsistent om legacy 1b-iii-redenen).
|
||||
|
||||
**Prioriteit:** Middel — geen blokker voor andere workstreams, maar elke
|
||||
maand dat dit blijft staan is een vlek op de boundaries-enforcement
|
||||
geloofwaardigheid. Aanbevolen: meelift met de eerste WS-3 PR die `lib/`
|
||||
of `plugins/` raakt, of een dedicated 2-3 uur sessie na WS-6 sluiting.
|
||||
|
||||
---
|
||||
|
||||
### TECH-DELETE-DEAD-VIEWS — Verwijder src/views/ uit apps/app/
|
||||
|
||||
**Aanleiding:** WS-3 sessie 1c audit (`dev-docs/WS-3-SESSION-1C-AUDIT.md`
|
||||
§A.1) constateerde dat `apps/app/src/views/` precies één bestand bevat
|
||||
(`views/pages/authentication/AuthProvider.vue`) met nul importers in de
|
||||
hele repo. Het is overgebleven Vuexy-template dode code. De §4.2
|
||||
post-consolidatie target layout drop `views/` volledig. Het bestand is
|
||||
nu in `boundaries/ignore` opgenomen om sessie 1c te laten landen, maar
|
||||
de natuurlijke vervolgstap is fysieke verwijdering.
|
||||
|
||||
**Wat:**
|
||||
- Verwijder `apps/app/src/views/` recursief.
|
||||
- Verwijder de bijbehorende `'src/views/**'` regel uit
|
||||
`apps/app/.eslintrc.cjs` `boundaries/ignore`.
|
||||
- Verifieer dat `pnpm lint`, `pnpm build` en `pnpm test` groen blijven.
|
||||
- Eén commit: `chore(cleanup): delete dead Vuexy views/ directory`.
|
||||
|
||||
**Prioriteit:** Laag — triviaal cleanup-item, kan in elke gerelateerde
|
||||
housekeeping-sprint meeliften (bijvoorbeeld vóór of na WS-3 PR-B).
|
||||
|
||||
---
|
||||
|
||||
### TECH-WS3-BOUNDARIES-SUBZONES — Sub-zone import-boundaries inside components/ and pages/
|
||||
|
||||
**Aanleiding:** WS-3 sessie 1c heeft top-level zone-boundaries in
|
||||
`apps/app/` neergezet via `eslint-plugin-boundaries`. De `/dev-docs/ARCH-CONSOLIDATION-2026-04.md`
|
||||
§4.2 target layout introduceert sub-zones binnen die top-level zones —
|
||||
specifiek `components/{organizer,portal,shared}/` en
|
||||
`pages/{(auth),portal,register,events,persons,organisations,platform}/`.
|
||||
De architecturale intent is dat `components/portal` niet uit
|
||||
`components/organizer` mag importeren (en vice versa), met `shared` als
|
||||
de gemeenschappelijke uitgang. Sessie 1c heeft die sub-zone
|
||||
enforcement bewust uitgesteld omdat de sub-folders nog niet bestaan;
|
||||
pre-emptieve rules op niet-bestaande directories worden stille dode
|
||||
config die drift.
|
||||
|
||||
**Wat:**
|
||||
- **Precondition:** WS-3 PR-B is gemerged en de §4.2 sub-folder
|
||||
structuur is gelandt (`components/{organizer,portal,shared}/` en
|
||||
`pages/{(auth),portal,...}/` bestaan fysiek met content).
|
||||
- Breid `boundaries/elements` in `apps/app/.eslintrc.cjs` uit met:
|
||||
- `{ type: 'components-organizer', pattern: 'src/components/organizer/**' }`
|
||||
- `{ type: 'components-portal', pattern: 'src/components/portal/**' }`
|
||||
- `{ type: 'components-shared', pattern: 'src/components/shared/**' }`
|
||||
- (ontworpen sub-zones voor `pages/` analoog)
|
||||
- Voeg per-sub-zone rules toe: `components-portal` en
|
||||
`components-organizer` mogen beide uit `components-shared` importeren,
|
||||
maar niet uit elkaar. `pages/portal/` mag niet uit `pages/events/`
|
||||
(en de andere organizer-pages) importeren, en omgekeerd.
|
||||
- Resolve violations die bij eerste activatie naar boven komen.
|
||||
- ETA: 1-2 uur zodra precondities ervoor liggen.
|
||||
|
||||
**Prioriteit:** Middel — preventieve architectuur-discipline voor de
|
||||
multi-tenant context-isolatie tussen organizer en portal UI-paden.
|
||||
Zonder deze rules is de kans groot dat een ontwikkelaar tijdens een
|
||||
PR-B follow-up onbewust portal- en organizer-componenten verstrengelt.
|
||||
|
||||
---
|
||||
|
||||
### TECH-WS3-BOUNDARIES-ROUTER-ZONE — Add `router/` zone to boundaries matrix
|
||||
|
||||
**Aanleiding:** WS-3 sessie 1c audit (§3 forward-compatibility) flagde
|
||||
dat de §4.2 target layout `src/plugins/1.router/` vervangt door een
|
||||
flat `src/router/`. De huidige boundaries-matrix in
|
||||
`apps/app/.eslintrc.cjs` mapt router-files naar de `plugins` zone
|
||||
(omdat ze fysiek in `src/plugins/1.router/` zitten). Zodra de
|
||||
verhuizing plaatsvindt — geplant in een latere WS-3 PR — moet de
|
||||
matrix-config dat reflecteren, anders vallen router-files buiten de
|
||||
`boundaries/elements` mapping en flag-stormt de plugin met "no rule
|
||||
found".
|
||||
|
||||
**Wat:** In dezelfde commit/PR die `src/plugins/1.router/` naar
|
||||
`src/router/` verhuist:
|
||||
|
||||
- Voeg toe aan `boundaries/elements` in `apps/app/.eslintrc.cjs`:
|
||||
```js
|
||||
{ type: 'router', pattern: 'src/router/**' },
|
||||
```
|
||||
Plaats vóór `plugins` in de array (first-match-wins ordering).
|
||||
- Voeg toe aan `boundaries/element-types` rules:
|
||||
```js
|
||||
{ from: 'router', allow: ['types', 'utils', 'lib', 'plugins', 'stores'] },
|
||||
```
|
||||
- Verifieer `pnpm lint` blijft op 0 problemen.
|
||||
|
||||
**Trigger:** "src/plugins/1.router/" → "src/router/" verhuizing (latere
|
||||
WS-3 PR, vermoedelijk PR-B als die de router-tree consolideert).
|
||||
|
||||
**Prioriteit:** Laag — geen actie tot de verhuizing plaatsvindt; dan
|
||||
verplicht 5-minute follow-up.
|
||||
|
||||
---
|
||||
|
||||
### TECH-08 — Paginated response meta wordt weggegooid in organizer composables
|
||||
|
||||
**Aanleiding:** `apps/app/src/composables/api/useSections.ts` en
|
||||
|
||||
335
dev-docs/WS-3-SESSION-1C-AUDIT.md
Normal file
335
dev-docs/WS-3-SESSION-1C-AUDIT.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# WS-3 Session 1c — Audit (Phase A)
|
||||
|
||||
**Date:** 2026-04-30
|
||||
**Branch:** `ws3-session-1c-boundaries`
|
||||
**Scope:** `apps/app/` only. `apps/portal/` is consolidated in WS-3 PR-B and gets boundaries config there.
|
||||
**Plugin:** `eslint-plugin-boundaries` (architectural import boundaries via ESLint).
|
||||
**Lint baseline at start:** 0 problems (per `pnpm lint` exit 0).
|
||||
|
||||
This document is the Phase A deliverable per the 1c session prompt. It
|
||||
establishes the proposed boundaries matrix from filesystem evidence and
|
||||
flags open questions for Bert before Phase C (implementation) starts.
|
||||
|
||||
## 1. Zone inventory (A.1)
|
||||
|
||||
Verified directly from `apps/app/src/`. Counts are `*.{ts,vue,tsx}`
|
||||
files only, excluding `*.d.ts`.
|
||||
|
||||
| Zone | Files | Representative path | Responsibility |
|
||||
| -------------- | ----: | ------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| `@core/` | 57 | (vendored Vuexy) | Vuexy reference code. Already in `ignorePatterns`. |
|
||||
| `@layouts/` | 21 | (vendored Vuexy layout primitives) | Vuexy layout helpers. Already in `ignorePatterns`. |
|
||||
| `assets/` | 0 | (images / svg only) | Static media. No `.ts`/`.vue` files. |
|
||||
| `components/` | 79 | `components/AppLoadingIndicator.vue` | Vue components. Sub-folders by domain (account-settings, events, shifts, …).|
|
||||
| `composables/` | 24 | `composables/useTimeSlotDropdown.ts` | Reusable Composition API logic. Includes `composables/api/*` (TanStack Query).|
|
||||
| `layouts/` | 16 | `layouts/PublicLayout.vue` | Page-shell layouts. Includes `layouts/components/*` (navbar / footer pieces).|
|
||||
| `lib/` | 5 | `lib/query-client.ts` | Low-level glue: `axios.ts`, `apiErrors.ts`, `query-client.ts`, helpers. |
|
||||
| `navigation/` | 2 | `navigation/horizontal/index.ts` | Pure declarative menu config. No imports. |
|
||||
| `pages/` | 35 | `pages/index.vue` | Route pages (file-based via `unplugin-vue-router`). |
|
||||
| `plugins/` | 11 | `plugins/webfontloader.ts` | Vue plugin wiring (router guards, vuetify init, iconify, pinia, …). |
|
||||
| `stores/` | 6 | `stores/useImpersonationStore.ts` | Pinia stores: auth, impersonation, notification, organisation, sectionsUi, shiftDetail. |
|
||||
| `styles/` | 0 | (SCSS only) | Stylesheets. No `.ts`/`.vue` files. |
|
||||
| `types/` | 19 | `types/formSchema.ts` | Pure TypeScript types and DTOs. |
|
||||
| `utils/` | 3 | `utils/deviceFingerprint.ts` | Pure helpers: constants, paginationMeta, deviceFingerprint. |
|
||||
| `views/` | 1 | `views/pages/authentication/AuthProvider.vue` | **Single dead Vuexy file** — zero importers in repo (verified via grep). |
|
||||
|
||||
Auto-generated `*.d.ts` files at `apps/app/` root and inside `src/`:
|
||||
- `auto-imports.d.ts`, `components.d.ts`, `env.d.ts`, `shims.d.ts`,
|
||||
`typed-router.d.ts` (apps/app root).
|
||||
- `src/reset.d.ts` (manually-written, single line: `import '@total-typescript/ts-reset'`).
|
||||
|
||||
All `*.d.ts` are already excluded from lint via `ignorePatterns: ['*.d.ts']`.
|
||||
The `boundaries/include` glob `src/**/*.{ts,vue,tsx}` (no `.d.ts`)
|
||||
will exclude them automatically too.
|
||||
|
||||
Orchestration roots (special — must be in `boundaries/ignore`):
|
||||
- `src/App.vue` (imports stores, @core helpers; the app root).
|
||||
- `src/main.ts` (createApp + plugin registration).
|
||||
|
||||
## 2. Proposed boundaries matrix (A.2)
|
||||
|
||||
Refined from the prompt's starting-point table based on actual import
|
||||
patterns observed in the codebase. Direction: **rows may import from
|
||||
columns**.
|
||||
|
||||
| from \\ to | types | utils | lib | plugins | composables | stores | navigation | components | layouts | pages |
|
||||
| -------------- | :---: | :---: | :-: | :-----: | :---------: | :----: | :--------: | :--------: | :-----: | :---: |
|
||||
| `types` | ✓ | | | | | | | | | |
|
||||
| `utils` | ✓ | ✓ | | | | | | | | |
|
||||
| `lib` | ✓ | ✓ | ✓ | | | ?¹ | | | | |
|
||||
| `plugins` | ✓ | ✓ | ✓ | ✓ | | ✓² | | | | |
|
||||
| `composables` | ✓ | ✓ | ✓ | | ✓ | ✓³ | | | | |
|
||||
| `stores` | ✓ | ✓ | ✓ | | ✓ | ✓ | | | | |
|
||||
| `navigation` | ✓ | ✓ | | | | | ✓ | | | |
|
||||
| `components` | ✓ | ✓ | ✓ | | ✓ | ✓ | | ✓ | | |
|
||||
| `layouts` | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | |
|
||||
| `pages` | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | |
|
||||
|
||||
`✓` on the diagonal = peer imports allowed (e.g. one component may import another).
|
||||
|
||||
External packages (`vue`, `vuetify`, `pinia`, `@tanstack/vue-query`,
|
||||
`vue-router`, `@vueuse/*`, etc.) and the vendored zones (`@core/**`,
|
||||
`@layouts/**`) are importable from anywhere. Encoded by leaving them
|
||||
out of `boundaries/elements` and setting `boundaries/no-unknown: 'off'`.
|
||||
|
||||
### Diff vs prompt's starting-point matrix, with rationale
|
||||
|
||||
| Change | Rationale |
|
||||
|---|---|
|
||||
| `composables` may import `stores` (✓³) — **added** | Two API composables read auth state: `composables/api/useOrganisations.ts:4` and `composables/api/useAuth.ts:3` both import `useAuthStore`. Standard Composition-API pattern (composable = stateful logic that may read store-backed sources). Disallowing this would force a non-trivial refactor with no architectural benefit. |
|
||||
| `plugins` may import `stores` (✓²) — **added** | Router guard reads auth + organisation: `plugins/1.router/guards.ts:2-3`. Auth guard genuinely needs the auth store. Disallowing forces a contortion (e.g. dynamic-import dance) with no benefit. |
|
||||
| `pages` is allowed to import `layouts` (✓) — **added** | The post-consolidation layout uses route meta to bind `OrganizerLayout`/`PortalLayout`/`PublicLayout` per page; some pages may also reference layout types. Cheap, harmless, forward-compatible with §4.2. |
|
||||
| `lib` may import `stores` (?¹) — **OPEN QUESTION** | `lib/axios.ts` currently has 2 static imports of stores: `useNotificationStore` (toast on errors) and `useOrganisationStore` (active-org header). See open question Q1 below. |
|
||||
| `views` zone removed — **dropped** | A.1 found `views/` contains a single dead Vuexy file (`AuthProvider.vue`) with zero importers. Treat as vendored / ignore alongside `@core` and `@layouts`. The §4.2 target layout drops `views/` entirely. |
|
||||
| `pages` may import peer `pages` — **disallow** | 0 actual cases today (verified). Cross-page imports indicate routing logic missing or shared component not yet extracted; should fail loudly. Aligns with prompt's starting matrix. |
|
||||
|
||||
### `boundaries/elements` config (proposed)
|
||||
|
||||
```js
|
||||
'boundaries/elements': [
|
||||
// Order matters: first match wins.
|
||||
{ type: 'types', pattern: 'src/types/**' },
|
||||
{ type: 'utils', pattern: 'src/utils/**' },
|
||||
{ type: 'lib', pattern: 'src/lib/**' },
|
||||
{ type: 'plugins', pattern: 'src/plugins/**' },
|
||||
{ type: 'composables', pattern: 'src/composables/**' },
|
||||
{ type: 'stores', pattern: 'src/stores/**' },
|
||||
{ type: 'navigation', pattern: 'src/navigation/**' },
|
||||
{ type: 'components', pattern: 'src/components/**' },
|
||||
{ type: 'layouts', pattern: 'src/layouts/**' },
|
||||
{ type: 'pages', pattern: 'src/pages/**' },
|
||||
],
|
||||
```
|
||||
|
||||
### `boundaries/ignore` (proposed)
|
||||
|
||||
```js
|
||||
'boundaries/ignore': [
|
||||
'src/@core/**', // vendored Vuexy
|
||||
'src/@layouts/**', // vendored Vuexy
|
||||
'src/views/**', // single dead file (A.1)
|
||||
'src/App.vue', // orchestration root
|
||||
'src/main.ts', // orchestration root
|
||||
'src/assets/**', // static media
|
||||
'src/styles/**', // SCSS
|
||||
'**/*.d.ts', // generated declarations
|
||||
],
|
||||
```
|
||||
|
||||
### `boundaries/element-types` rules (proposed, for Phase C)
|
||||
|
||||
```js
|
||||
'boundaries/element-types': ['error', {
|
||||
default: 'disallow',
|
||||
rules: [
|
||||
{ from: 'types', allow: ['types'] },
|
||||
{ from: 'utils', allow: ['types', 'utils'] },
|
||||
{ from: 'lib', allow: ['types', 'utils', 'lib' /*, 'stores' if Q1=yes */] },
|
||||
{ from: 'plugins', allow: ['types', 'utils', 'lib', 'plugins', 'stores'] },
|
||||
{ from: 'composables', allow: ['types', 'utils', 'lib', 'composables', 'stores'] },
|
||||
{ from: 'stores', allow: ['types', 'utils', 'lib', 'composables', 'stores'] },
|
||||
{ from: 'navigation', allow: ['types', 'utils', 'navigation'] },
|
||||
{ from: 'components', allow: ['types', 'utils', 'lib', 'composables', 'stores', 'components'] },
|
||||
{ from: 'layouts', allow: ['types', 'utils', 'lib', 'composables', 'stores', 'navigation', 'components', 'layouts'] },
|
||||
{ from: 'pages', allow: ['types', 'utils', 'lib', 'composables', 'stores', 'navigation', 'components', 'layouts'] },
|
||||
],
|
||||
}],
|
||||
```
|
||||
|
||||
## 3. Forward-compatibility check vs §4.2 (A.3)
|
||||
|
||||
The post-consolidation target layout (`/dev-docs/ARCH-CONSOLIDATION-2026-04.md`
|
||||
§4.2) introduces sub-folders inside several existing zones. Verifying
|
||||
the proposed config doesn't flag those as violations the moment they
|
||||
land:
|
||||
|
||||
| §4.2 sub-zone | Resolves under proposed config to | Forward-compat? |
|
||||
|-----------------------------------------------------------|-----------------------------------|:---------------:|
|
||||
| `pages/(auth)/`, `pages/portal/`, `pages/register/`, `pages/events/`, `pages/persons/`, `pages/organisations/`, `pages/platform/` | `pages` (pattern `src/pages/**`) | ✓ |
|
||||
| `components/{organizer,portal,shared}/` | `components` (pattern `src/components/**`) | ✓ |
|
||||
| `composables/forms/` (replaces `packages/form-schema/`) | `composables` (pattern `src/composables/**`) | ✓ |
|
||||
| `layouts/OrganizerLayout.vue` / `PortalLayout.vue` / `PublicLayout.vue` | `layouts` (already exists post-1a) | ✓ |
|
||||
| `router/index.ts`, `router/routes.ts` (new top-level zone) | NOT in current config — see note below | n/a |
|
||||
| `plugins/theme.ts` | `plugins` (pattern `src/plugins/**`) | ✓ |
|
||||
|
||||
**Note on the new `router/` zone.** The §4.2 target replaces
|
||||
`src/plugins/1.router/` with a flat `src/router/`. When that move
|
||||
happens (in a later WS-3 PR), `.eslintrc.cjs` will need a new entry:
|
||||
|
||||
```js
|
||||
{ type: 'router', pattern: 'src/router/**' },
|
||||
```
|
||||
|
||||
…with the rule `{ from: 'router', allow: ['types', 'utils', 'lib', 'plugins', 'stores'] }`.
|
||||
**Not adding pre-emptively in 1c** because the directory doesn't exist
|
||||
yet — pre-emptive rules become silent dead config that drifts. Will be
|
||||
added in the same PR that moves the router files. Flagging here for
|
||||
visibility.
|
||||
|
||||
**Sub-zone enforcement** (e.g. "components/portal must not import from
|
||||
components/organizer") is **explicitly out of scope** for 1c per the
|
||||
prompt and §4.2 layout-target. Backlogged below.
|
||||
|
||||
## 4. Plugin selection + install plan (A.4)
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Package | `eslint-plugin-boundaries` |
|
||||
| Latest version | **6.0.2** |
|
||||
| License | MIT (permissive ✓) |
|
||||
| `peerDependencies` | `eslint: '>=6.0.0'` — compatible with our `eslint@8.57.1` ✓ |
|
||||
| `engines` | `node: '>=18.18'` — compatible with local `v22.22.1` ✓ |
|
||||
| Install style | exact pin (e.g. `"eslint-plugin-boundaries": "6.0.2"`), matching the rest of `apps/app/package.json` `eslint-plugin-*` entries |
|
||||
| Install command | `pnpm add -D eslint-plugin-boundaries@6.0.2` from `apps/app/` |
|
||||
| Direct devDep? | **YES** — same pattern as the 14 existing `eslint-plugin-*` entries; satisfies the `TECH-PORTAL-ESLINT-DEPS` lesson (Cursor's ESLint extension uses strict module resolution and silently fails on plugins reachable only via pnpm hoisting) |
|
||||
|
||||
## 5. Estimated violation count + top offenders (A.5)
|
||||
|
||||
`pnpm add --no-save` is not supported (pnpm rejects the flag), so the
|
||||
fallback static-grep approach from the prompt was used. Static greps
|
||||
covered all matrix-relevant cross-zone import directions.
|
||||
|
||||
**Under the proposed matrix** (Q1 = "yes, allow `lib → stores`"):
|
||||
|
||||
| Direction probed | Count | Notes |
|
||||
|---|---:|---|
|
||||
| `components` → `pages` | 0 | clean |
|
||||
| `composables` → `components` | 0 | clean |
|
||||
| `stores` → `components` | 0 | clean |
|
||||
| `lib` → `utils` | 0 | (utils currently empty of consumers) |
|
||||
| `utils` → any internal | 0 | true leaf zone |
|
||||
| `types` → non-`types` | 0 | only peer-types imports |
|
||||
| `components` → `layouts` | 0 | clean |
|
||||
| `pages` → peer `pages` | 0 | clean |
|
||||
| `lib` → `stores` | 2 | **the open-question case** — see Q1 |
|
||||
| `composables` → `stores` | 2 | allowed by proposed matrix (rationale §2) |
|
||||
| `plugins` → `stores` | 2 | allowed by proposed matrix (rationale §2) |
|
||||
| Cross-zone relative imports (`../../`, `../zone/`) | 0 | All cross-zone imports use the `@/` alias |
|
||||
|
||||
**Final estimate**:
|
||||
- If Q1 answer is **"yes"** (allow `lib → stores`): **0 violations**.
|
||||
Phase C ships clean with no code moves.
|
||||
- If Q1 answer is **"no"** (disallow): **2 violations**, both in
|
||||
`apps/app/src/lib/axios.ts` (the static imports of
|
||||
`useNotificationStore` line 3 and `useOrganisationStore` line 4).
|
||||
Phase C must refactor — see Q1 below for two refactor sketches.
|
||||
|
||||
The codebase is exceptionally clean re: architectural boundaries.
|
||||
Earlier WS-3 lint cleanup (1b-i/ii/iii) and the natural Composition-
|
||||
API patterns kept the directional graph mostly acyclic. The only
|
||||
non-trivial decision is the `lib ↔ stores` cross-cut.
|
||||
|
||||
## 6. Open questions for Bert
|
||||
|
||||
### Q1. `lib → stores`: allow, or refactor `lib/axios.ts`?
|
||||
|
||||
**Context.** `apps/app/src/lib/axios.ts` has two **static** imports of
|
||||
stores (lines 3-4):
|
||||
|
||||
```ts
|
||||
import { useNotificationStore } from '@/stores/useNotificationStore'
|
||||
import { useOrganisationStore } from '@/stores/useOrganisationStore'
|
||||
```
|
||||
|
||||
These are used in:
|
||||
- The request interceptor (sets `X-Organisation-Id` header from
|
||||
`useOrganisationStore`).
|
||||
- The response interceptor (calls `notificationStore.show(...)` for
|
||||
toast on 403/404/422/503/5xx/!response).
|
||||
|
||||
The same file already uses **dynamic** `await import('@/stores/...')`
|
||||
for the auth/impersonation flows (the 1b-iii fix). So the pattern
|
||||
"axios.ts reaches into stores" is already partially established —
|
||||
just inconsistently.
|
||||
|
||||
**Three valid resolutions:**
|
||||
|
||||
- **A. Allow `lib → stores` in the matrix.** Pragmatic; matches
|
||||
current code. The boundaries rule reflects reality: `lib` and
|
||||
`stores` are both infrastructure layers and `lib/axios.ts` is the
|
||||
cross-cutting HTTP-↔-state seam. **Phase C ships clean.**
|
||||
Trade-off: the layered model becomes less strict — a future contributor
|
||||
could put a less-justified `lib → store` import in any other lib
|
||||
file. Mitigation: keep the rule; add a comment in `axios.ts`
|
||||
documenting it's the deliberate exception.
|
||||
- **B. Disallow `lib → stores`; convert the 2 static imports to
|
||||
dynamic.** Refactor `lib/axios.ts` lines 3-4 to remove the static
|
||||
imports and `await import('@/stores/...')` inside each interceptor
|
||||
callback (matching the auth flow's pattern). Two-file edit (just
|
||||
`axios.ts`). Resolves the boundary violation without a comment-as-
|
||||
exception. Trade-off: every interceptor invocation pays the dynamic-
|
||||
import cost (cached after first call, so amortized to zero). Slightly
|
||||
uglier code (the `useOrganisationStore` call is on every API request,
|
||||
not just error paths).
|
||||
- **C. Disallow `lib → stores`; extract the seam to a new zone.**
|
||||
Make a thin `lib/http-bindings.ts` (or move axios fully to a new
|
||||
`services/` layer that's allowed to import stores). More invasive;
|
||||
not warranted for two imports.
|
||||
|
||||
**Recommendation: A.** Keep the boundary loose for `lib → stores`,
|
||||
ship clean, document the intent in the matrix's rule comment. The
|
||||
strict-layering prize isn't worth the dynamic-import dance for code
|
||||
that's been stable for months. If a future code-review sees a NEW
|
||||
`lib/X.ts` that imports a store without the same axios-level
|
||||
justification, the reviewer flags it; the lint rule isn't the only
|
||||
gate.
|
||||
|
||||
### Q2. `views/` — confirm: ignore (treat as vendored)?
|
||||
|
||||
`views/` contains exactly one file (`views/pages/authentication/AuthProvider.vue`)
|
||||
which has zero importers in the repo. It's Vuexy-template dead code.
|
||||
The §4.2 target layout drops `views/` entirely. **Recommendation:
|
||||
add `src/views/**` to `boundaries/ignore`.** Confirm.
|
||||
|
||||
(Tangent: a future cleanup PR could just delete `src/views/` outright.
|
||||
Not in scope for 1c. Could be a `chore:` follow-up — flagging for
|
||||
backlog awareness, not asking for sign-off here.)
|
||||
|
||||
### Q3. `navigation` allowed to import from where?
|
||||
|
||||
`navigation/horizontal/index.ts` and `navigation/vertical/index.ts`
|
||||
currently import nothing — they are pure declarative menu data. The
|
||||
proposed matrix allows `navigation → types, utils`. This is forward-
|
||||
defensive: a future nav config might need a route-name type from
|
||||
`types/`. **Confirm or tighten to "navigation imports nothing".**
|
||||
|
||||
(Default recommendation: keep `types, utils`. Cost = zero, headroom = real.)
|
||||
|
||||
### Q4. Sub-zone enforcement scope for the consolidation sprint
|
||||
|
||||
§4.2 introduces `components/{organizer,portal,shared}` and
|
||||
`pages/{(auth),portal,register,...}`. The intent: enforce
|
||||
"components/portal must not import from components/organizer" and
|
||||
similar tenant-isolation rules. **Confirmed out of scope for 1c per
|
||||
the prompt** — flagging for an explicit "yes, this is a backlog item
|
||||
for after PR-B" sign-off. Proposed backlog entry:
|
||||
|
||||
> **TECH-WS3-BOUNDARIES-SUBZONES** — Sub-zone import boundaries
|
||||
> inside `components/` and `pages/` (organizer ⛔ portal, shared ✓)
|
||||
> for the post-consolidation §4.2 layout. Preconditions: WS-3 PR-B
|
||||
> consolidation merged and the §4.2 sub-folder structure landed.
|
||||
> Approach: extend `boundaries/elements` with `{ type: 'components-organizer', pattern: 'src/components/organizer/**' }`
|
||||
> etc., and add per-sub-zone rules. ETA: 1-2h once preconditions met.
|
||||
|
||||
## 7. STOP — handoff to Phase B
|
||||
|
||||
This audit is read-only. No `.eslintrc.cjs` edit. No `package.json`
|
||||
edit. No code edits.
|
||||
|
||||
**To proceed to Phase C**, Bert needs to confirm:
|
||||
1. The proposed matrix in §2 is acceptable (or amend it).
|
||||
2. Q1 (lib → stores): A, B, or C?
|
||||
3. Q2 (views/ ignored): yes/no?
|
||||
4. Q3 (navigation imports): keep as proposed (`types, utils`) or tighten?
|
||||
5. Q4 (sub-zone enforcement = backlog item, not 1c work): confirm.
|
||||
|
||||
Once Bert signs off in chat, Phase C executes per the prompt's C.1–C.7
|
||||
sequence. Expected outcome:
|
||||
- 1 commit: `chore(deps): add eslint-plugin-boundaries to apps/app`.
|
||||
- 1 commit: `chore(tooling): enable eslint-plugin-boundaries in apps/app`.
|
||||
- 0–1 commit: `refactor(apps/app): resolve N boundary violations`
|
||||
(only if Q1 = B or C; otherwise omitted).
|
||||
- 1 commit: `docs(ws3): record session 1c completion (boundaries enforcement)`.
|
||||
- Acceptance: `pnpm lint` exit 0, `pnpm build` succeeds, `pnpm test`
|
||||
remains 49 passed, no file in `apps/portal/`/`api/`/`packages/`/
|
||||
`docs/` modified.
|
||||
Reference in New Issue
Block a user