diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index e2312f71..04bc2270 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -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' + ] + } + } + } + ] + } + ] + } } ); diff --git a/frontend/src/lib/shared/discussion/CommentMessage.svelte b/frontend/src/lib/shared/discussion/CommentMessage.svelte index b0b22017..2feae96d 100644 --- a/frontend/src/lib/shared/discussion/CommentMessage.svelte +++ b/frontend/src/lib/shared/discussion/CommentMessage.svelte @@ -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'; diff --git a/frontend/src/lib/shared/discussion/MentionDropdown.svelte b/frontend/src/lib/shared/discussion/MentionDropdown.svelte index 0230564e..4932eda4 100644 --- a/frontend/src/lib/shared/discussion/MentionDropdown.svelte +++ b/frontend/src/lib/shared/discussion/MentionDropdown.svelte @@ -1,5 +1,6 @@