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
3 changed files with 82 additions and 0 deletions
Showing only changes of commit 09e0fd8fa2 - Show all commits

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';
@@ -61,5 +62,84 @@ export default defineConfig(
}
]
}
},
{
plugins: { boundaries },
settings: {
'import/resolver': { typescript: { project: './tsconfig.json' } },
'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

@@ -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';