feat(eslint): add boundaries/dependencies rule for frontend domain imports #429

Merged
marcel merged 8 commits from feat/issue-410-eslint-boundary-rule into main 2026-05-05 18:09:26 +02:00
20 changed files with 918 additions and 91 deletions

View File

@@ -185,3 +185,40 @@ Quick reminders:
- No premature abstractions — KISS beats DRY
- No backwards-compatibility shims for code that has no callers
- Validate at system boundaries only (user input, external APIs)
## Frontend Domain Boundaries
The frontend mirrors the backend's package-by-domain structure. Each Tier-1 folder under `src/lib/` is a domain with a hard import boundary:
```
document person tag user geschichte notification ocr
activity conversation shared
```
The `boundaries/dependencies` ESLint rule enforces this. The full allow-list lives in `frontend/eslint.config.js`. The rule fires at error severity and blocks `npm run lint`.
### Allowed cross-domain imports
| From | May import from |
|---|---|
| `document` | `shared`, `person`, `tag`, `ocr`, `activity`, `conversation` |
| `geschichte` | `shared`, `person`, `document` |
| `ocr` | `shared`, `document` |
| `activity` | `shared`, `notification` |
| `person`, `tag`, `user`, `notification`, `conversation` | `shared` only |
| `shared` | `shared` only |
| `routes` | any domain |
### When you need to cross a boundary
1. **Move the code to `$lib/shared/`** — the correct fix when the code is truly generic (a UI primitive, a pure utility, a formatting helper).
2. **Add an explicit rule** — if a cross-domain dependency is architecturally justified (e.g., `document` importing `PersonTypeahead`), add the allow entry to `eslint.config.js` with a comment explaining the reason.
3. **Use `// eslint-disable-next-line boundaries/dependencies`** — last resort, only for cases where neither option is practical. Leave a comment explaining why.
### Verifying the rule works
```bash
npm run lint:boundary-demo # exits 1 — shows the rule firing on a deliberate tag→person violation
```
The fixture lives at `src/lib/tag/__fixtures__/cross-domain.fixture.ts` and is excluded from `npm run lint` via `--ignore-pattern`.

View File

@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import boundaries from 'eslint-plugin-boundaries';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import ts from 'typescript-eslint';
@@ -12,7 +13,16 @@ const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default defineConfig(
includeIgnoreFile(gitignorePath),
{ ignores: ['src/paraglide/**', '.svelte-kit.old/**'] },
{
ignores: [
'src/paraglide/**',
'.svelte-kit.old/**',
// Fixture files are intentionally invalid imports used to demonstrate
// that the boundaries/dependencies rule fires. Exclude them from the
// regular lint pass so `npm run lint` stays green.
'src/lib/**/__fixtures__/**'
]
},
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
@@ -61,5 +71,87 @@ export default defineConfig(
}
]
}
},
{
plugins: { boundaries },
settings: {
'import/resolver': { typescript: { project: './tsconfig.json' } },
// $lib/paraglide and $lib/generated are intentionally omitted from the elements list —
// they are treated as external (third-party-like) by the rule and may be imported
// from any domain without restriction.
'boundaries/elements': [
{ type: 'document', pattern: 'src/lib/document/**' },
{ type: 'person', pattern: 'src/lib/person/**' },
{ type: 'tag', pattern: 'src/lib/tag/**' },
{ type: 'user', pattern: 'src/lib/user/**' },
{ type: 'geschichte', pattern: 'src/lib/geschichte/**' },
{ type: 'notification', pattern: 'src/lib/notification/**' },
{ type: 'ocr', pattern: 'src/lib/ocr/**' },
{ type: 'activity', pattern: 'src/lib/activity/**' },
{ type: 'conversation', pattern: 'src/lib/conversation/**' },
{ type: 'shared', pattern: 'src/lib/shared/**' },
{ type: 'routes', pattern: 'src/routes/**' }
]
},
rules: {
'boundaries/dependencies': [
'error',
{
default: 'disallow',
message:
"Cross-domain import blocked. Move shared code to $lib/shared/, or expose it via the domain's index.ts.",
rules: [
// Document composes person components (D-FE-1) and tag components (D-FE-2),
// and hosts the OCR trigger in the transcription editor.
{
from: { type: 'document' },
allow: {
to: { type: ['shared', 'conversation', 'activity', 'person', 'tag', 'ocr'] }
}
},
// Geschichte editor selects persons and documents by design.
{
from: { type: 'geschichte' },
allow: { to: { type: ['shared', 'person', 'document'] } }
},
// OCR trigger embeds the document script-type selector.
{
from: { type: 'ocr' },
allow: { to: { type: ['shared', 'document'] } }
},
// Activity feed (Chronik) reads notification items for its inbox panel.
{
from: { type: 'activity' },
allow: { to: { type: ['shared', 'notification'] } }
},
{ from: { type: 'person' }, allow: { to: { type: ['shared'] } } },
{ from: { type: 'tag' }, allow: { to: { type: ['shared'] } } },
{ from: { type: 'user' }, allow: { to: { type: ['shared'] } } },
{ from: { type: 'notification' }, allow: { to: { type: ['shared'] } } },
{ from: { type: 'conversation' }, allow: { to: { type: ['shared'] } } },
{ from: { type: 'shared' }, allow: { to: { type: ['shared'] } } },
{
from: { type: 'routes' },
allow: {
to: {
type: [
'document',
'person',
'tag',
'user',
'geschichte',
'notification',
'ocr',
'activity',
'conversation',
'shared'
]
}
}
}
]
}
]
}
}
);

View File

@@ -34,6 +34,8 @@
"@vitest/coverage-v8": "^4.1.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-boundaries": "6.0.2",
"eslint-plugin-svelte": "^3.13.0",
"globals": "^16.5.0",
"openapi-typescript": "^7.8.0",
@@ -194,6 +196,23 @@
"dev": true,
"license": "MIT"
},
"node_modules/@boundaries/elements": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@boundaries/elements/-/elements-2.0.1.tgz",
"integrity": "sha512-sAWO3D8PFP6pBXdxxW93SQi/KQqqhE2AAHo3AgWfdtJXwO6bfK6/wUN81XnOZk0qRC6vHzUEKhjwVD9dtDWvxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-import-resolver-node": "0.3.9",
"eslint-module-utils": "2.12.1",
"handlebars": "4.7.9",
"is-core-module": "2.16.1",
"micromatch": "4.0.8"
},
"engines": {
"node": ">=18.18"
}
},
"node_modules/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
@@ -340,6 +359,40 @@
"node": ">=20.19.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.4",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
@@ -1415,6 +1468,19 @@
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@playwright/test": {
"version": "1.58.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
@@ -2799,6 +2865,17 @@
"@tiptap/pm": "3.22.5"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -3151,6 +3228,275 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@unrs/resolver-binding-android-arm-eabi": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
"integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@unrs/resolver-binding-android-arm64": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
"integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@unrs/resolver-binding-darwin-arm64": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
"integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@unrs/resolver-binding-darwin-x64": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
"integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@unrs/resolver-binding-freebsd-x64": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
"integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
"integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
"integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
"integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-arm64-musl": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
"integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
"integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
"integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
"integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
"integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-x64-gnu": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
"integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-linux-x64-musl": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
"integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@unrs/resolver-binding-wasm32-wasi": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
"integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^0.2.11"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
"integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
"integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@unrs/resolver-binding-win32-x64-msvc": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
"integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@vitest/browser": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.0.tgz",
@@ -3538,6 +3884,19 @@
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4009,6 +4368,137 @@
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-import-context": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz",
"integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-tsconfig": "^4.10.1",
"stable-hash-x": "^0.2.0"
},
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-import-context"
},
"peerDependencies": {
"unrs-resolver": "^1.0.0"
},
"peerDependenciesMeta": {
"unrs-resolver": {
"optional": true
}
}
},
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
"integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^3.2.7",
"is-core-module": "^2.13.0",
"resolve": "^1.22.4"
}
},
"node_modules/eslint-import-resolver-node/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/eslint-import-resolver-typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz",
"integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==",
"dev": true,
"license": "ISC",
"dependencies": {
"debug": "^4.4.1",
"eslint-import-context": "^0.1.8",
"get-tsconfig": "^4.10.1",
"is-bun-module": "^2.0.0",
"stable-hash-x": "^0.2.0",
"tinyglobby": "^0.2.14",
"unrs-resolver": "^1.7.11"
},
"engines": {
"node": "^16.17.0 || >=18.6.0"
},
"funding": {
"url": "https://opencollective.com/eslint-import-resolver-typescript"
},
"peerDependencies": {
"eslint": "*",
"eslint-plugin-import": "*",
"eslint-plugin-import-x": "*"
},
"peerDependenciesMeta": {
"eslint-plugin-import": {
"optional": true
},
"eslint-plugin-import-x": {
"optional": true
}
}
},
"node_modules/eslint-module-utils": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
"integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^3.2.7"
},
"engines": {
"node": ">=4"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/eslint-module-utils/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/eslint-plugin-boundaries": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-boundaries/-/eslint-plugin-boundaries-6.0.2.tgz",
"integrity": "sha512-wSHgiYeMEbziP91lH0UQ9oslgF2djG1x+LV9z/qO19ggMKZaCB8pKIGePHAY91eLF4EAgpsxQk8MRSFGRPfPzw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@boundaries/elements": "2.0.1",
"chalk": "4.1.2",
"eslint-import-resolver-node": "0.3.9",
"eslint-module-utils": "2.12.1",
"handlebars": "4.7.9",
"micromatch": "4.0.8"
},
"engines": {
"node": ">=18.18"
},
"peerDependencies": {
"eslint": ">=6.0.0"
}
},
"node_modules/eslint-plugin-svelte": {
"version": "3.15.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.15.2.tgz",
@@ -4238,6 +4728,19 @@
"node": ">=16.0.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -4301,6 +4804,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -4334,6 +4850,28 @@
"dev": true,
"license": "ISC"
},
"node_modules/handlebars": {
"version": "4.7.9",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"neo-async": "^2.6.2",
"source-map": "^0.6.1",
"wordwrap": "^1.0.0"
},
"bin": {
"handlebars": "bin/handlebars"
},
"engines": {
"node": ">=0.4.7"
},
"optionalDependencies": {
"uglify-js": "^3.1.4"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4450,6 +4988,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-bun-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
"integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.7.1"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -4496,6 +5044,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -5103,6 +5661,33 @@
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"license": "CC0-1.0"
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
@@ -5126,6 +5711,16 @@
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -5172,6 +5767,22 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/napi-postinstall": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
"integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
"dev": true,
"license": "MIT",
"bin": {
"napi-postinstall": "lib/cli.js"
},
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/napi-postinstall"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5179,6 +5790,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"license": "MIT"
},
"node_modules/node-readable-to-web-readable-stream": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz",
@@ -5909,6 +6527,16 @@
"node": ">=4"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rollup": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
@@ -6050,6 +6678,16 @@
"node": ">=18"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -6071,6 +6709,16 @@
"kysely": "*"
}
},
"node_modules/stable-hash-x": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz",
"integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -6320,6 +6968,19 @@
"integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==",
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -6367,6 +7028,14 @@
"typescript": ">=4.8.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6431,6 +7100,20 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"bin": {
"uglifyjs": "bin/uglifyjs"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/undici": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
@@ -6463,6 +7146,41 @@
"node": ">=18.12.0"
}
},
"node_modules/unrs-resolver": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
"integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"napi-postinstall": "^0.3.0"
},
"funding": {
"url": "https://opencollective.com/unrs-resolver"
},
"optionalDependencies": {
"@unrs/resolver-binding-android-arm-eabi": "1.11.1",
"@unrs/resolver-binding-android-arm64": "1.11.1",
"@unrs/resolver-binding-darwin-arm64": "1.11.1",
"@unrs/resolver-binding-darwin-x64": "1.11.1",
"@unrs/resolver-binding-freebsd-x64": "1.11.1",
"@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
"@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
"@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
"@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
"@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
"@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
"@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
"@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
"@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
"@unrs/resolver-binding-linux-x64-musl": "1.11.1",
"@unrs/resolver-binding-wasm32-wasi": "1.11.1",
"@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
"@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
"@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6845,6 +7563,13 @@
"node": ">=0.10.0"
}
},
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"dev": true,
"license": "MIT"
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",

View File

@@ -12,6 +12,7 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"lint:boundary-demo": "eslint src/lib/tag/__fixtures__/",
"test:unit": "vitest",
"test": "npm run test:unit -- --run",
"test:coverage": "vitest run --coverage --project=server",
@@ -47,6 +48,8 @@
"@vitest/coverage-v8": "^4.1.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
"eslint-plugin-boundaries": "6.0.2",
"eslint-plugin-svelte": "^3.13.0",
"globals": "^16.5.0",
"openapi-typescript": "^7.8.0",

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import TagInput, { type Tag } from '$lib/tag/TagInput.svelte';
import FieldLabelBadge from './FieldLabelBadge.svelte';
import FieldLabelBadge from '$lib/shared/primitives/FieldLabelBadge.svelte';
import { m } from '$lib/paraglide/messages.js';
let {

View File

@@ -1,7 +1,9 @@
<script lang="ts">
import { statusDotClass, statusLabel } from '$lib/person/personFormat';
type DocumentStatus = 'PLACEHOLDER' | 'UPLOADED' | 'TRANSCRIBED' | 'REVIEWED' | 'ARCHIVED';
import {
formatDocumentStatus,
statusDotClass,
type DocumentStatus
} from '$lib/document/documentStatusLabel';
type Props = {
status: DocumentStatus;
@@ -10,7 +12,7 @@ type Props = {
let { status }: Props = $props();
const dotClass = $derived(statusDotClass(status));
const label = $derived(statusLabel(status));
const label = $derived(formatDocumentStatus(status));
</script>
<span

View File

@@ -2,7 +2,7 @@
import { onMount, untrack } from 'svelte';
import PersonTypeahead from '$lib/person/PersonTypeahead.svelte';
import PersonMultiSelect from '$lib/person/PersonMultiSelect.svelte';
import FieldLabelBadge from './FieldLabelBadge.svelte';
import FieldLabelBadge from '$lib/shared/primitives/FieldLabelBadge.svelte';
import { isoToGerman, handleGermanDateInput } from '$lib/shared/utils/date';
import { m } from '$lib/paraglide/messages.js';
import type { components } from '$lib/generated/api';

View File

@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { formatDocumentStatus } from './documentStatusLabel';
import { formatDocumentStatus, statusDotClass } from './documentStatusLabel';
describe('formatDocumentStatus', () => {
it('maps PLACEHOLDER to correct label', () => {
@@ -26,3 +26,25 @@ describe('formatDocumentStatus', () => {
expect(formatDocumentStatus('SOMETHING_NEW')).toBe('Unbekannt');
});
});
describe('statusDotClass', () => {
it('PLACEHOLDER → bg-gray-400', () => {
expect(statusDotClass('PLACEHOLDER')).toBe('bg-gray-400');
});
it('UPLOADED → bg-emerald-500', () => {
expect(statusDotClass('UPLOADED')).toBe('bg-emerald-500');
});
it('TRANSCRIBED → bg-blue-400', () => {
expect(statusDotClass('TRANSCRIBED')).toBe('bg-blue-400');
});
it('REVIEWED → bg-amber-400', () => {
expect(statusDotClass('REVIEWED')).toBe('bg-amber-400');
});
it('ARCHIVED → bg-emerald-600', () => {
expect(statusDotClass('ARCHIVED')).toBe('bg-emerald-600');
});
});

View File

@@ -1,9 +1,7 @@
import { m } from '$lib/paraglide/messages.js';
/**
* Maps a document status string to a localised human-readable label.
* Falls back to "Unknown" for unrecognised values.
*/
export type DocumentStatus = 'PLACEHOLDER' | 'UPLOADED' | 'TRANSCRIBED' | 'REVIEWED' | 'ARCHIVED';
export function formatDocumentStatus(status: string): string {
switch (status) {
case 'PLACEHOLDER':
@@ -20,3 +18,18 @@ export function formatDocumentStatus(status: string): string {
return m.doc_status_unknown();
}
}
export function statusDotClass(status: DocumentStatus): string {
switch (status) {
case 'PLACEHOLDER':
return 'bg-gray-400';
case 'UPLOADED':
return 'bg-emerald-500';
case 'TRANSCRIBED':
return 'bg-blue-400';
case 'REVIEWED':
return 'bg-amber-400';
case 'ARCHIVED':
return 'bg-emerald-600';
}
}

View File

@@ -4,7 +4,7 @@ import type { components } from '$lib/generated/api';
import { m } from '$lib/paraglide/messages.js';
import { clickOutside } from '$lib/shared/actions/clickOutside';
import { createTypeahead } from '$lib/shared/hooks/useTypeahead.svelte';
import FieldLabelBadge from '$lib/document/FieldLabelBadge.svelte';
import FieldLabelBadge from '$lib/shared/primitives/FieldLabelBadge.svelte';
type Person = components['schemas']['Person'];
interface Props {

View File

@@ -1,12 +1,5 @@
import { describe, it, expect } from 'vitest';
import {
getInitials,
abbreviateName,
formatXsMeta,
personAvatarColor,
statusDotClass,
statusLabel
} from './personFormat';
import { getInitials, abbreviateName, formatXsMeta, personAvatarColor } from './personFormat';
import { formatDate } from '$lib/shared/utils/date';
// ─── getInitials ─────────────────────────────────────────────────────────────
@@ -146,51 +139,3 @@ describe('formatDate', () => {
expect(formatDate('1944-01-01', 'short')).toBe('01.01.1944');
});
});
// ─── statusDotClass ──────────────────────────────────────────────────────────
describe('statusDotClass', () => {
it('PLACEHOLDER → bg-gray-400', () => {
expect(statusDotClass('PLACEHOLDER')).toBe('bg-gray-400');
});
it('UPLOADED → bg-emerald-500', () => {
expect(statusDotClass('UPLOADED')).toBe('bg-emerald-500');
});
it('TRANSCRIBED → bg-blue-400', () => {
expect(statusDotClass('TRANSCRIBED')).toBe('bg-blue-400');
});
it('REVIEWED → bg-amber-400', () => {
expect(statusDotClass('REVIEWED')).toBe('bg-amber-400');
});
it('ARCHIVED → bg-emerald-600', () => {
expect(statusDotClass('ARCHIVED')).toBe('bg-emerald-600');
});
});
// ─── statusLabel ─────────────────────────────────────────────────────────────
describe('statusLabel', () => {
it('PLACEHOLDER → "Platzhalter"', () => {
expect(statusLabel('PLACEHOLDER')).toBe('Platzhalter');
});
it('UPLOADED → "Hochgeladen"', () => {
expect(statusLabel('UPLOADED')).toBe('Hochgeladen');
});
it('TRANSCRIBED → "Transkribiert"', () => {
expect(statusLabel('TRANSCRIBED')).toBe('Transkribiert');
});
it('REVIEWED → "Geprüft"', () => {
expect(statusLabel('REVIEWED')).toBe('Geprüft');
});
it('ARCHIVED → "Archiviert"', () => {
expect(statusLabel('ARCHIVED')).toBe('Archiviert');
});
});

View File

@@ -1,8 +1,6 @@
import { formatDocumentStatus } from '$lib/document/documentStatusLabel';
import { formatDate } from '$lib/shared/utils/date';
type Person = { firstName?: string | null; lastName: string; displayName: string };
type DocumentStatus = 'PLACEHOLDER' | 'UPLOADED' | 'TRANSCRIBED' | 'REVIEWED' | 'ARCHIVED';
type DocForMeta = {
sender?: Person | null;
receivers?: Person[];
@@ -75,22 +73,3 @@ export function formatXsMeta(doc: DocForMeta): string {
export function personAvatarColor(personId: string): string {
return AVATAR_PALETTE[djb2(personId) % AVATAR_PALETTE.length];
}
export function statusDotClass(status: DocumentStatus): string {
switch (status) {
case 'PLACEHOLDER':
return 'bg-gray-400';
case 'UPLOADED':
return 'bg-emerald-500';
case 'TRANSCRIBED':
return 'bg-blue-400';
case 'REVIEWED':
return 'bg-amber-400';
case 'ARCHIVED':
return 'bg-emerald-600';
}
}
export function statusLabel(status: string): string {
return formatDocumentStatus(status);
}

View File

@@ -2,6 +2,7 @@
import { m } from '$lib/paraglide/messages.js';
import type { FlatMessage } from '$lib/shared/types';
import { extractQuote } from '$lib/shared/discussion/comment';
// eslint-disable-next-line boundaries/dependencies -- discussion UI needs person initials for avatars; move to shared if getInitials becomes generic
import { getInitials } from '$lib/person/personFormat';
import { relativeTime } from '$lib/shared/utils/time';
import { renderBody } from '$lib/shared/discussion/mention';

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import type { components } from '$lib/generated/api';
// eslint-disable-next-line boundaries/dependencies -- mention dropdown needs person date formatting; extract to shared if it becomes reusable
import { formatLifeDateRange } from '$lib/person/personLifeDates';
import { m } from '$lib/paraglide/messages.js';

View File

@@ -0,0 +1,7 @@
// Deliberate boundary violation: tag domain importing from person domain.
// This file exists to prove the boundaries/dependencies rule fires.
// It is excluded from `npm run lint` via --ignore-pattern and must NEVER be imported by production code.
// Run `npm run lint:boundary-demo` to see ESLint report the violation (exit 1).
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { getInitials } from '$lib/person/personFormat';

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import DropZone from './DropZone.svelte';
import DashboardResumeStrip from '$lib/shared/dashboard/DashboardResumeStrip.svelte';
import MissionControlStrip from '$lib/shared/dashboard/MissionControlStrip.svelte';
import MissionControlStrip from '$lib/document/MissionControlStrip.svelte';
import DashboardFamilyPulse from '$lib/shared/dashboard/DashboardFamilyPulse.svelte';
import DashboardActivityFeed from '$lib/activity/DashboardActivityFeed.svelte';
import EnrichmentBlock from '$lib/document/EnrichmentBlock.svelte';