fix(i-d): Geister-Komponenten eliminiert — AIAssistant-Seite erstellt (Agent-Auswahl + AgentChat, STATIC_COMPONENT_MAP registriert nach C3-Pattern); 5 Ghost-Contact-Detail-Tabs aus Backend-Manifesten entfernt; Production-Build mit AIAssistant-Chunk verifiziert (AIAssistant-DVb66TSo.js); tsc exit=0; ruff clean
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
This commit is contained in:
@@ -102,6 +102,8 @@
|
|||||||
**Block C ABGESCHLOSSEN** — C1–C8 implementiert, Gate-C-Checks 4+5 bewiesen; Checks 1–3 (Playwright gegen Build, Workspace-E2E, Normal-User-E2E) erfordern E2E-Lauf → Block E.
|
**Block C ABGESCHLOSSEN** — C1–C8 implementiert, Gate-C-Checks 4+5 bewiesen; Checks 1–3 (Playwright gegen Build, Workspace-E2E, Normal-User-E2E) erfordern E2E-Lauf → Block E.
|
||||||
**Block D: D1+D2 ABGESCHLOSSEN** (6d04206) — D1: alle 9 Ziel-Suites grün (auth 10/10, abac, companies 18/18, contacts 8/8, calendar 34/34, ai_proactive 38+1s, api_tokens, backend_coverage_gaps 41/41, phase_h_wiki 42/42); 3 Produktionsbugs behoben (ContactCreate-Typ-Inferenz, CalendarContract-Zugriffe ×4, SystemSettings-Schema-Drift+Migration 0142). Offen: D3 (API-/Testpfade + ARCH-051/055/056/057), D4 (Security-Triage BUG-019/020 + ARCH-027), D5 (Marathon-Triage), D6 (Legacy-Migration ARCH-059/023).
|
**Block D: D1+D2 ABGESCHLOSSEN** (6d04206) — D1: alle 9 Ziel-Suites grün (auth 10/10, abac, companies 18/18, contacts 8/8, calendar 34/34, ai_proactive 38+1s, api_tokens, backend_coverage_gaps 41/41, phase_h_wiki 42/42); 3 Produktionsbugs behoben (ContactCreate-Typ-Inferenz, CalendarContract-Zugriffe ×4, SystemSettings-Schema-Drift+Migration 0142). Offen: D3 (API-/Testpfade + ARCH-051/055/056/057), D4 (Security-Triage BUG-019/020 + ARCH-027), D5 (Marathon-Triage), D6 (Legacy-Migration ARCH-059/023).
|
||||||
|
|
||||||
|
| E/I-D | Geister-Komponenten eliminiert: @/pages/AIAssistant gebaut (minimale Seite mit Agent-Auswahl + AgentChat, in STATIC_COMPONENT_MAP registriert — C3-Pattern); 5 Contact-Detail-Tabs (ContactCalendarTab/FilesTab/LinksTab/MailTab/TagsTab) aus Backend-Manifesten entfernt (Features bleiben über Haupt-Seiten erreichbar) | ✅ tsc --noEmit exit=0; Production-Build exit=0 mit AIAssistant-Chunks (AIAssistant-DVb66TSo.js 5.92 kB); ruff clean ×6 Dateien; create_app OK (560 routes); Route /ai-assistant funktioniert statt ErrorBoundary | — |
|
||||||
|
|
||||||
### Bekannte Vorbestände (Block D Triage)
|
### Bekannte Vorbestände (Block D Triage)
|
||||||
- ~~9 Contact/Company-Test-Failures~~ ✅ GELÖST in D1-b (ContactCreate-Typ-Inferenz, 9d8da99) — Root-Cause war BUG-008-Validator-Default type='company'.
|
- ~~9 Contact/Company-Test-Failures~~ ✅ GELÖST in D1-b (ContactCreate-Typ-Inferenz, 9d8da99) — Root-Cause war BUG-008-Validator-Default type='company'.
|
||||||
- ~~test_mail: 28 Failures 'Unknown entity type: mail_account'~~ ✅ Root-Cause in D3-d behoben (ef90d57): Mail/DMS get_entity_models-Overrides fehlten; Rest-Failures im vollen Mail-Lauf = IMAP-Netzwerk-Calls ohne Mocking (Test-Infrastruktur-Follow-up, kein Produktionsbug).
|
- ~~test_mail: 28 Failures 'Unknown entity type: mail_account'~~ ✅ Root-Cause in D3-d behoben (ef90d57): Mail/DMS get_entity_models-Overrides fehlten; Rest-Failures im vollen Mail-Lauf = IMAP-Netzwerk-Calls ohne Mocking (Test-Infrastruktur-Follow-up, kein Produktionsbug).
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
from app.plugins.base import BasePlugin
|
from app.plugins.base import BasePlugin
|
||||||
from app.plugins.manifest import (
|
from app.plugins.manifest import (
|
||||||
FrontendDashboardWidget,
|
FrontendDashboardWidget,
|
||||||
FrontendDetailTab,
|
|
||||||
FrontendMenuItem,
|
FrontendMenuItem,
|
||||||
FrontendPageRoute,
|
FrontendPageRoute,
|
||||||
PluginManifest,
|
PluginManifest,
|
||||||
@@ -66,9 +65,9 @@ class CalendarPlugin(BasePlugin):
|
|||||||
page_routes=[
|
page_routes=[
|
||||||
FrontendPageRoute(path='/calendar', component='@/pages/Calendar', protected=True, permission='calendar:read'),
|
FrontendPageRoute(path='/calendar', component='@/pages/Calendar', protected=True, permission='calendar:read'),
|
||||||
],
|
],
|
||||||
detail_tabs=[
|
# BUG (ghost component): ContactCalendarTab does not exist in the
|
||||||
FrontendDetailTab(entity_type='contact', label_key='tabs.calendar', label='Calendar', component='@/components/contact/ContactCalendarTab', icon='Calendar', order=30, permission='calendar:read'),
|
# frontend — tab removed until implemented (Block I-D).
|
||||||
],
|
detail_tabs=[],
|
||||||
author="LeoCRM Team",
|
author="LeoCRM Team",
|
||||||
min_app_version="1.0.0",
|
min_app_version="1.0.0",
|
||||||
hooks=["calendar.before_appointment", "calendar.after_appointment"],
|
hooks=["calendar.before_appointment", "calendar.after_appointment"],
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from app.plugins.base import BasePlugin
|
from app.plugins.base import BasePlugin
|
||||||
from app.plugins.manifest import (
|
from app.plugins.manifest import (
|
||||||
FrontendDetailTab,
|
|
||||||
FrontendMenuItem,
|
FrontendMenuItem,
|
||||||
FrontendPageRoute,
|
FrontendPageRoute,
|
||||||
PluginManifest,
|
PluginManifest,
|
||||||
@@ -45,9 +44,9 @@ class DmsPlugin(BasePlugin):
|
|||||||
FrontendPageRoute(path='/dms', component='@/pages/Dms', protected=True, permission='dms:read'),
|
FrontendPageRoute(path='/dms', component='@/pages/Dms', protected=True, permission='dms:read'),
|
||||||
FrontendPageRoute(path='/dms/trash', component='@/pages/DmsTrash', protected=True, permission='dms:read'),
|
FrontendPageRoute(path='/dms/trash', component='@/pages/DmsTrash', protected=True, permission='dms:read'),
|
||||||
],
|
],
|
||||||
detail_tabs=[
|
# BUG (ghost component): ContactFilesTab does not exist in the
|
||||||
FrontendDetailTab(entity_type='contact', label_key='tabs.files', label='Dateien', component='@/components/contact/ContactFilesTab', icon='FolderOpen', order=40, permission='dms:read'),
|
# frontend — tab removed until implemented (Block I-D).
|
||||||
],
|
detail_tabs=[],
|
||||||
author="LeoCRM Team",
|
author="LeoCRM Team",
|
||||||
min_app_version="1.0.0",
|
min_app_version="1.0.0",
|
||||||
hooks=["dms.before_upload"],
|
hooks=["dms.before_upload"],
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from app.plugins.base import BasePlugin
|
from app.plugins.base import BasePlugin
|
||||||
from app.plugins.manifest import FrontendDetailTab, PluginManifest, PluginRouteDef
|
from app.plugins.manifest import PluginManifest, PluginRouteDef
|
||||||
|
|
||||||
|
|
||||||
class EntityLinksPlugin(BasePlugin):
|
class EntityLinksPlugin(BasePlugin):
|
||||||
@@ -42,9 +42,9 @@ class EntityLinksPlugin(BasePlugin):
|
|||||||
"entity_links:delete",
|
"entity_links:delete",
|
||||||
],
|
],
|
||||||
is_core=True,
|
is_core=True,
|
||||||
detail_tabs=[
|
# BUG (ghost component): ContactLinksTab does not exist in the
|
||||||
FrontendDetailTab(entity_type='contact', label_key='tabs.links', label='Verknüpfungen', component='@/components/contact/ContactLinksTab', icon='Link', order=60, permission='entity_links:read'),
|
# frontend — tab removed until implemented (Block I-D).
|
||||||
],
|
detail_tabs=[],
|
||||||
|
|
||||||
author="LeoCRM Team",
|
author="LeoCRM Team",
|
||||||
min_app_version="1.0.0",
|
min_app_version="1.0.0",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from typing import Any
|
|||||||
|
|
||||||
from app.plugins.base import BasePlugin
|
from app.plugins.base import BasePlugin
|
||||||
from app.plugins.manifest import (
|
from app.plugins.manifest import (
|
||||||
FrontendDetailTab,
|
|
||||||
FrontendMenuItem,
|
FrontendMenuItem,
|
||||||
FrontendPageRoute,
|
FrontendPageRoute,
|
||||||
FrontendSettingsPage,
|
FrontendSettingsPage,
|
||||||
@@ -150,9 +149,9 @@ class MailPlugin(BasePlugin):
|
|||||||
settings_pages=[
|
settings_pages=[
|
||||||
FrontendSettingsPage(path='mail', label_key='settings.mail', label='Mail', component='@/pages/MailSettings', icon='Mail', order=50),
|
FrontendSettingsPage(path='mail', label_key='settings.mail', label='Mail', component='@/pages/MailSettings', icon='Mail', order=50),
|
||||||
],
|
],
|
||||||
detail_tabs=[
|
# BUG (ghost component): ContactMailTab does not exist in the
|
||||||
FrontendDetailTab(entity_type='contact', label_key='tabs.email', label='E-Mails', component='@/components/contact/ContactMailTab', icon='Mail', order=20, permission='mail:read'),
|
# frontend — tab removed until implemented (Block I-D).
|
||||||
],
|
detail_tabs=[],
|
||||||
author="LeoCRM Team",
|
author="LeoCRM Team",
|
||||||
min_app_version="1.0.0",
|
min_app_version="1.0.0",
|
||||||
hooks=["mail.before_send", "mail.after_send"],
|
hooks=["mail.before_send", "mail.after_send"],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from app.plugins.base import BasePlugin
|
from app.plugins.base import BasePlugin
|
||||||
from app.plugins.manifest import FrontendDetailTab, PluginManifest, PluginRouteDef
|
from app.plugins.manifest import PluginManifest, PluginRouteDef
|
||||||
|
|
||||||
|
|
||||||
class TagsPlugin(BasePlugin):
|
class TagsPlugin(BasePlugin):
|
||||||
@@ -31,9 +31,9 @@ class TagsPlugin(BasePlugin):
|
|||||||
"tags:admin",
|
"tags:admin",
|
||||||
],
|
],
|
||||||
is_core=True,
|
is_core=True,
|
||||||
detail_tabs=[
|
# BUG (ghost component): ContactTagsTab does not exist in the
|
||||||
FrontendDetailTab(entity_type='contact', label_key='tabs.tags', label='Tags', component='@/components/contact/ContactTagsTab', icon='Tag', order=50, permission='tags:read'),
|
# frontend — tab removed until implemented (Block I-D).
|
||||||
],
|
detail_tabs=[],
|
||||||
|
|
||||||
author="LeoCRM Team",
|
author="LeoCRM Team",
|
||||||
min_app_version="1.0.0",
|
min_app_version="1.0.0",
|
||||||
|
|||||||
@@ -1197,3 +1197,9 @@ Playwright E2E: 10 passed, 24 failed (BUG-011/012/013 — Mock-Daten und data-te
|
|||||||
|
|
||||||
### G3-a Nachtrag (Block I-H): dump.rdb
|
### G3-a Nachtrag (Block I-H): dump.rdb
|
||||||
- ✅ Erledigt 2026-08-24 (Block E/I-H): dump.rdb aus Repo-Root entfernt (war git-ignored, kein Leak); Root-Cause bewiesen — lokaler Test-Redis hatte workdir=Repo-Root; workdir jetzt auf /tmp gesetzt. Production ist nicht betroffen (docker-compose nutzt redisdata:/data Volume).
|
- ✅ Erledigt 2026-08-24 (Block E/I-H): dump.rdb aus Repo-Root entfernt (war git-ignored, kein Leak); Root-Cause bewiesen — lokaler Test-Redis hatte workdir=Repo-Root; workdir jetzt auf /tmp gesetzt. Production ist nicht betroffen (docker-compose nutzt redisdata:/data Volume).
|
||||||
|
|
||||||
|
## Geister-Komponenten — STATUS
|
||||||
|
✅ Gefixt 2026-08-24 (Block E/I-D):
|
||||||
|
- @/pages/AIAssistant: Minimale Seite erstellt (frontend/src/pages/AIAssistant.tsx, Agent-Auswahl + AgentChat), in STATIC_COMPONENT_MAP registriert (C3-Pattern); Production-Build erzeugt eigenen Chunk AIAssistant-DVb66TSo.js (5.92 kB) — Route /ai-assistant funktioniert jetzt statt ErrorBoundary.
|
||||||
|
- 5 Contact-Detail-Tabs (ContactCalendarTab/FilesTab/LinksTab/MailTab/TagsTab): Aus den Backend-Manifesten von calendar/dms/entity_links/mail/tags entfernt (detail_tabs=[] mit Erklärungskommentar). Die Features bleiben über die Haupt-Seiten erreichbar; Tabs können bei Bedarf als Feature implementiert werden.
|
||||||
|
Verifikation: tsc --noEmit exit=0, Production-Build exit=0 mit AIAssistant-Chunk, ruff clean, create_app OK (560 routes).
|
||||||
|
|||||||
@@ -118,8 +118,8 @@ function normalizeModule(m: ComponentModule): { default: React.ComponentType<any
|
|||||||
const STATIC_COMPONENT_MAP: Record<string, LazyComponentFactory> = {
|
const STATIC_COMPONENT_MAP: Record<string, LazyComponentFactory> = {
|
||||||
// Pages
|
// Pages
|
||||||
'@/pages/AgentDashboard': () => import('@/pages/AgentDashboard').then(normalizeModule),
|
'@/pages/AgentDashboard': () => import('@/pages/AgentDashboard').then(normalizeModule),
|
||||||
// NOTE: @/pages/AIAssistant is declared in the backend manifest but does
|
// BUG (ghost page) fixed in Block I-D: page now exists.
|
||||||
// not exist as a frontend page — ghost component, see PROGRESS.md.
|
'@/pages/AIAssistant': () => import('@/pages/AIAssistant').then(normalizeModule),
|
||||||
'@/pages/AISettings': () => import('@/pages/AISettings').then(normalizeModule),
|
'@/pages/AISettings': () => import('@/pages/AISettings').then(normalizeModule),
|
||||||
'@/pages/AutomationDashboard': () => import('@/pages/AutomationDashboard').then(normalizeModule),
|
'@/pages/AutomationDashboard': () => import('@/pages/AutomationDashboard').then(normalizeModule),
|
||||||
'@/pages/AutomationSettings': () => import('@/pages/AutomationSettings').then(normalizeModule),
|
'@/pages/AutomationSettings': () => import('@/pages/AutomationSettings').then(normalizeModule),
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAgents } from '@/api/automation';
|
||||||
|
import { Card } from '@/components/ui/Card';
|
||||||
|
import { AgentChat } from '@/components/agents/AgentChat';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Assistant page (BUG: ghost page fixed — was declared in the backend
|
||||||
|
* manifest but did not exist, see PROGRESS.md "Ghost components").
|
||||||
|
*
|
||||||
|
* Minimal implementation: pick an agent, chat with it. Full agent management
|
||||||
|
* lives in the Agent Dashboard.
|
||||||
|
*/
|
||||||
|
export default function AIAssistant() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: agents, isLoading } = useAgents();
|
||||||
|
const [selectedAgentId, setSelectedAgentId] = useState('');
|
||||||
|
|
||||||
|
const selectedAgent = agents?.find((a) => a.id === selectedAgentId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 space-y-4" aria-label={t('ai.assistant', 'AI Assistant')}>
|
||||||
|
<h1 className="text-xl font-semibold text-secondary-900">
|
||||||
|
{t('ai.assistant', 'AI Assistant')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Card className="p-4">
|
||||||
|
<label
|
||||||
|
htmlFor="assistant-agent-select"
|
||||||
|
className="block text-sm font-medium text-secondary-700 mb-1"
|
||||||
|
>
|
||||||
|
{t('agent.targetAgent', 'Target agent')}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="assistant-agent-select"
|
||||||
|
value={selectedAgentId}
|
||||||
|
onChange={(e) => setSelectedAgentId(e.target.value)}
|
||||||
|
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
<option value="">{t('agent.selectTargetAgent', 'Select an agent…')}</option>
|
||||||
|
{(agents ?? []).map((agent) => (
|
||||||
|
<option key={agent.id} value={agent.id}>
|
||||||
|
{agent.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{!isLoading && (agents ?? []).length === 0 && (
|
||||||
|
<p className="mt-2 text-sm text-secondary-500">
|
||||||
|
{t(
|
||||||
|
'agent.noAgentsHint',
|
||||||
|
'No agents yet — create one in the Agent Dashboard.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{selectedAgent && (
|
||||||
|
<Card className="p-4">
|
||||||
|
<AgentChat agentId={selectedAgent.id} agentName={selectedAgent.name} />
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user