feat(livia)!: Exposé-Funktion vollständig entfernt

Runde 8 hat Livia zur Research- & Market-Intelligence-Agentin gemacht. Die
Exposé-Erstellung war der Kern ihrer alten Rolle und fällt mit ihr weg — sie
wird nicht nachgeordnet weitergeführt. Ein zweiter Reiter hätte die Funktion
sichtbar gelassen und die neue Positionierung damit halbiert.

Entfernt: 23 Dateien (Arbeitsbereich, dreistufiger Assistent, Dossier mit elf
Bereichen, Provider, Services, Hooks, Feldkatalog, Mockdaten, deterministischer
Textbaustein) sowie `generateExposeText` aus `IAIService` und beiden
Implementierungen.

Livias Arbeitsplatz zeigt jetzt nur noch die Recherche-Leads — ohne
Reiterbalken, weil eine Leiste mit einem Eintrag eine Alternative behauptet,
die es nicht gibt. Ihr Personalblatt, die Bearbeitungsverlaufs-Einträge und die
Teamdokumentation sind auf die neue Rolle nachgezogen; die beiden
Beispielvorgänge zeigen jetzt eine Leaderzeugung und ein Review mit
widersprüchlichen Quellen statt Lagebeschrieb und Exposétext.

Bei Nora entfällt die Mehrfachauswahl in der Objektliste: sie diente allein der
Weiterleitung an die Exposé-Erstellung. Eine Auswahl ohne Ziel wäre ein
Bedienelement, das nichts auslöst. Die Matching-Darstellung selbst bleibt
unverändert.

Der Wiederaufbau ist in `archive/livia/` beschrieben; das Manifest hält jetzt
fest, welche seiner Annahmen eingetreten sind und welche nicht — Livia bleibt
im Team, nur die Funktion ist weg.

Geprüft: tsc 0 Fehler, eslint 0 Fehler/0 Warnungen, 423/423 Tests grün,
Produktionsbuild erfolgreich, Smoke-Test über sieben Seiten ohne
Laufzeitfehler, 12/12 Abnahmekriterien erfüllt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Opus 5 committed 2026-08-23 22:44:08 +02:00
1 parent f5451f649f
commit a219d3f2ec
33 files changed
+178 -2474

No files matched your search

+9 -57
View File
@@ -1,9 +1,7 @@
import { useCallback, useMemo, useState } from 'react'
import { Alert, Box, Button, Chip, Divider, Typography } from '@mui/material'
import { CheckCircle2, ExternalLink, Forward, MapPin, Ruler } from 'lucide-react'
import { useMemo } from 'react'
import { Alert, Box, Chip, Divider, Typography } from '@mui/material'
import { CheckCircle2, ExternalLink, MapPin, Ruler } from 'lucide-react'
import type { MarketLead } from '../../hooks/useMarketLeads'
import { useCreateExposeLead } from '../../hooks/useExposeLeads'
import { useToastStore } from '../../stores/toastStore'
import { AgentAvatar, ObjectDeepLink } from '../team'
import { agentWorkspaceById } from '../../lib/agentWorkspaces'
import { SOURCE_TYPE_LABELS } from '../../lib/constants'
@@ -30,16 +28,15 @@ const LIVIA = agentWorkspaceById('livia')!
interface LeadDetailProps {
lead: MarketLead
/** Matching-Block und Weiterleitung anzeigen. Bei Nora ja, bei Livia nein (§1). */
/** Matching-Block anzeigen. Bei Nora ja, bei Livia nein (§1). */
showMatching?: boolean
}
/**
* Wrapper, der die Detailansicht über die Signal-ID neu aufbaut.
*
* Damit ist die Objektauswahl an das Signal gebunden, ohne sie in einem Effekt
* zurücksetzen zu müssen — ein neues Signal ist eine neue Komponente, und die
* startet ohnehin ohne Auswahl.
* Ein neues Signal ist damit eine neue Komponente und startet mit frischem
* Zustand, statt ihn in einem Effekt zurücksetzen zu müssen.
*/
function LeadDetail({ lead, showMatching = true }: LeadDetailProps) {
return <LeadDetailBody key={lead.signal.id} lead={lead} showMatching={showMatching} />
@@ -55,10 +52,6 @@ function LeadDetailBody({ lead, showMatching }: { lead: MarketLead; showMatching
const rationale = signalRationaleOf(signal)
const action = recommendedActionOf(signal)
const forwardLead = useCreateExposeLead()
const showToast = useToastStore(s => s.showToast)
const [selectedIds, setSelectedIds] = useState<string[]>([])
// Objektvorschläge unterhalb der Schwelle werden gar nicht erst gezeigt.
const relevant = useMemo(
() =>
@@ -70,29 +63,6 @@ function LeadDetailBody({ lead, showMatching }: { lead: MarketLead; showMatching
const contacts = useMemo(() => confirmableContacts(signal.extractedContacts), [signal.extractedContacts])
const toggle = useCallback((id: string) => {
setSelectedIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id])
}, [])
function handleForward() {
const count = selectedIds.length
forwardLead.mutate(
{
prospect: companyName,
contacts: contacts.map(c => c.value),
locationHint: signal.locationHint,
propertyIds: selectedIds,
sourceSignalId: signal.id,
},
{
onSuccess: () => {
setSelectedIds([])
showToast(`Zur Exposé-Erstellung vorgemerkt — ${count} Objekt(e) für ${companyName}.`, 'success')
},
},
)
}
return (
<Box sx={{ height: '100%', overflowY: 'auto' }}>
{/* Header — ohne Wahrscheinlichkeitsangabe (§7.3) */}
@@ -270,27 +240,9 @@ function LeadDetailBody({ lead, showMatching }: { lead: MarketLead; showMatching
Kein Portfolioobjekt über {MIN_MATCH_PCT}% Flächenmatch — manuelle Prüfung empfohlen
</Alert>
) : (
<>
{relevant.map(({ p, fit }) => (
<SignalPropertyRow
key={p.id}
p={p}
fit={fit}
selected={selectedIds.includes(p.id)}
onToggle={() => toggle(p.id)}
/>
))}
<Button
variant="contained"
startIcon={<Forward size={15} />}
disabled={selectedIds.length === 0 || forwardLead.isPending}
onClick={handleForward}
sx={{ mt: 1, textTransform: 'none', bgcolor: DS_BRAND.main, '&:hover': { bgcolor: DS_BRAND.hoverAlt } }}
>
Für Exposé-Erstellung vormerken
{selectedIds.length > 0 && ` (${selectedIds.length})`}
</Button>
</>
relevant.map(({ p, fit }) => (
<SignalPropertyRow key={p.id} p={p} fit={fit} />
))
)}
</Box>
</>