docs(agenten): Fallakte Livia und Agenten-Briefing sichern
`LIVIA_PROFIL_UND_PROMPT.md` hält Livia als Exposé Master fest, wie sie vor Runde 8 war: Personalblatt, Aufgabenkatalog, Kanäle und Systeme, das Exposé-Dossier mit elf Bereichen und neun Pflichtfeldern, die `missingFacts`-Regel der Textmaschine — dazu ein System-Prompt, mit dem sich diese Fassung ausserhalb des Repositories rekonstruieren lässt. `archive/livia/` ergänzt die Beschreibung um die Wiederherstellung: die 23 Dateien der Exposé-Funktion als wörtliche Kopie im Original-Pfadlayout sowie ein Manifest jeder Stelle, an der Livia in geteilten Dateien verdrahtet war. Beides ist mit Runde 8 nicht überholt, sondern der Referenzstand: die Exposé-Funktion wurde nicht entfernt, sondern nachgeordnet. Das Manifest beschreibt allerdings den Rückbau eines vollständigen Entfernens und damit ein Szenario, das so nicht eingetreten ist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ba8dc39483
commit
2037e5a463
33 files changed
+4431
No files matched your search
@@ -0,0 +1,138 @@
|
||||
import { Box, Chip, MenuItem, TextField, Typography } from '@mui/material'
|
||||
import type { ExposeField, ExposeSection } from '../../lib/exposeFields'
|
||||
import type { ExposeValues } from '../../domain/expose'
|
||||
import { DS_SLATE, DS_TEXT } from '../../lib/ds'
|
||||
|
||||
interface Props {
|
||||
section: ExposeSection
|
||||
values: ExposeValues
|
||||
missing: Set<string>
|
||||
onChange: (key: string, value: string) => void
|
||||
/** Zusatzinhalt am Ende des Abschnitts, z. B. der KI-Entwurfsknopf. */
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
/** Mehrfachauswahl als Wert: kommagetrennt, damit ein flaches Wörterbuch reicht. */
|
||||
function toggleChip(current: string, option: string): string {
|
||||
const set = new Set(current.split(',').map(s => s.trim()).filter(Boolean))
|
||||
if (set.has(option)) set.delete(option)
|
||||
else set.add(option)
|
||||
return [...set].join(', ')
|
||||
}
|
||||
|
||||
function isSelected(current: string, option: string): boolean {
|
||||
return current.split(',').map(s => s.trim()).includes(option)
|
||||
}
|
||||
|
||||
function FieldControl({
|
||||
field, value, isMissing, onChange,
|
||||
}: {
|
||||
field: ExposeField
|
||||
value: string
|
||||
isMissing: boolean
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
const label = field.suffix ? `${field.label} (${field.suffix})` : field.label
|
||||
|
||||
if (field.type === 'chips') {
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{ display: 'block', mb: 0.75, color: DS_TEXT.secondary, textTransform: 'uppercase', letterSpacing: 0.4, fontSize: '0.65rem' }}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
|
||||
{(field.options ?? []).map(o => {
|
||||
const selected = isSelected(value, o)
|
||||
return (
|
||||
<Chip
|
||||
key={o}
|
||||
label={o}
|
||||
size="small"
|
||||
variant={selected ? 'filled' : 'outlined'}
|
||||
onClick={() => onChange(toggleChip(value, o))}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
bgcolor: selected ? '#152642' : 'transparent',
|
||||
color: selected ? 'white' : DS_TEXT.secondary,
|
||||
borderColor: DS_SLATE[200],
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
select={field.type === 'select'}
|
||||
multiline={field.type === 'multiline'}
|
||||
rows={field.type === 'multiline' ? 3 : undefined}
|
||||
type={field.type === 'number' ? 'number' : 'text'}
|
||||
label={field.required ? `${label} *` : label}
|
||||
size="small"
|
||||
fullWidth
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-notchedOutline': isMissing
|
||||
? { borderColor: DS_TEXT.error, borderWidth: 2 }
|
||||
: {},
|
||||
}}
|
||||
>
|
||||
{field.type === 'select' &&
|
||||
(field.options ?? []).map(o => <MenuItem key={o} value={o}>{o}</MenuItem>)}
|
||||
</TextField>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein fachlicher Bereich des Exposé-Dossiers.
|
||||
*
|
||||
* Rendert generisch aus `lib/exposeFields.ts` — die Bereiche sind Daten, keine
|
||||
* elf handgeschriebenen Formulare. Leere Pflichtfelder sind rot umrandet
|
||||
* (Runde 4, §8.5.2); sie werden nicht mit plausiblen Werten gefüllt.
|
||||
*/
|
||||
export function ExposeFieldGroup({ section, values, missing, onChange, footer }: Props) {
|
||||
return (
|
||||
<Box component="section" sx={{ mb: 4 }}>
|
||||
<Typography sx={{ fontWeight: 700, fontSize: '1rem', color: DS_TEXT.primary }}>
|
||||
{section.title}
|
||||
</Typography>
|
||||
{section.description && (
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.muted, display: 'block', mb: 1.5 }}>
|
||||
{section.description}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', md: 'repeat(3, minmax(0, 1fr))' },
|
||||
gap: 1.5,
|
||||
mt: section.description ? 0 : 1.5,
|
||||
}}
|
||||
>
|
||||
{section.fields.map(field => (
|
||||
<Box
|
||||
key={field.key}
|
||||
sx={{ gridColumn: field.wide || field.type === 'chips' || field.type === 'multiline' ? '1 / -1' : 'auto' }}
|
||||
>
|
||||
<FieldControl
|
||||
field={field}
|
||||
value={values[field.key] ?? ''}
|
||||
isMissing={missing.has(field.key)}
|
||||
onChange={v => onChange(field.key, v)}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{footer && <Box sx={{ mt: 1.5 }}>{footer}</Box>}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user