Files
Benjamin Sutter d200d4e930 feat(agenten): Runde 4 — «Meine Agenten» mit fünf digitalen Mitarbeitenden
Setzt das Umsetzungsbriefing Runde 4 im bestehenden Frontend um.

Informationsarchitektur
- «Teamübersicht» heisst «Meine Agenten»; ihr bisheriger Inhalt (Kreisgrafik,
  Auswertung, Verbindungszusammenfassung) ist entfallen.
- Personalverwaltung, Bearbeitungsverlauf und Kanäle & Systeme sind jetzt
  Reiter derselben Seite (?section=), gestaltet wie die Auswahl im
  Bearbeitungsverlauf. Es wird nur der gewählte Bereich gerendert.
- Die alten Unterseitenpfade leiten um, damit Lesezeichen nicht brechen.

Agentenbestand
- Reto und Lea vollständig entfernt — aus Navigation, Dossiers, Protokoll,
  Verbindungen, Vorgängen und Porträtbestand.
- Retos Aufgaben liegen bei Bruno: Nachbereitung, WhatsApp-Anruf,
  Protokoll und Kundennotiz, Ablage im CRM.
- Livia ist «Exposé Master»: Lageberichte, Inserate, Angebotsbroschüren.
- Sidebar führt Ferdi, Bruno, Livia, Nora, Sina mit Porträt und Funktion.

Agentenseiten
- Ein gemeinsamer AgentWorkspaceHero auf allen fünf Seiten.
- Ferdi: Auswertungskarten, Priorität und Typfarben entfallen; Fälligkeit nur
  bei fünf Tagen oder weniger rot; Objektlinks nach «Meine Objekte»; neu die
  Terminplanung im verbundenen Kalender mit typgerechtem PDF-Ausschnitt.
- Sina: reduzierte, filter- und sortierbare Objektübersicht; Detailansicht
  direkt editierbar, leere Pflichtfelder rot umrandet.
- Nora: Signale ohne Prozentsätze und Konfidenzstufen, nur belegbare Angaben;
  Mehrfachauswahl leitet Objekte an Livia weiter.
- Livia: aktive und archivierte Leads, Arbeitsbereich gleitet an den oberen
  Rand; dreistufiger Exposé-Prozess Hochladen → Exposé → Export.
- Bruno: neue Seite mit Auftragsliste und Vor-/Nachbereitungs-Drawer;
  Glocke warnt bei Besichtigung unter 24 Stunden ohne Bericht.

Datenschicht
- Neu: Kalender, Exposé-Leads, Exposé-Entwürfe, Besichtigungsaufträge —
  je Domain, Provider, Service und Hook.
- IAIService um generateExposeText erweitert; der Entwurf nutzt ausschliesslich
  erfasste Objektdaten und meldet Lücken, statt sie zu füllen.
- Alle Objektverweise zeigen auf reale Einträge aus «Meine Objekte»; neue
  Detailroute /supply/properties/:propertyId.

Offen: Chat, Kalender, CRM und DMS sind Frontend-Simulation ohne Anbindung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:43:16 +02:00

75 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
/**
* check-tokens.js
*
* Counts hardcoded hex color literals in sx props / color attributes
* across src/components and src/pages. Fails (exit 1) if count exceeds THRESHOLD.
*
* Usage:
* node scripts/check-tokens.js — report + fail if > threshold
* node scripts/check-tokens.js --report — report only, always exits 0
*
* Run via: npm run check:tokens
*/
import { readdirSync, readFileSync, statSync } from 'node:fs'
import { join, extname } from 'node:path'
// Hex literals allowed before CI blocks the build.
// This is a ratchet — lower it as migration progresses. Never raise it.
// Baseline after initial token migration (2026-05-24): 1958
// Nach der Token-Migration aller Farb-Properties (bgcolor/color/borderColor/
// background/fill/stroke): 925. Die verbleibenden Treffer stehen in
// Farbverläufen, `rgba()`-Werten, Icon-Attributen und Datentabellen, die diese
// Zählung mitnimmt, die ESLint-Regel aber nicht erfasst.
const THRESHOLD = 925
const HEX_PATTERN = /#[0-9A-Fa-f]{3,8}\b/g
const SEARCH_DIRS = ['src/components', 'src/pages']
function walk(dir) {
const files = []
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const full = join(dir, entry.name)
if (entry.isDirectory()) files.push(...walk(full))
else if (entry.isFile() && ['.ts', '.tsx'].includes(extname(entry.name))) files.push(full)
}
return files
}
const reportOnly = process.argv.includes('--report')
const cwd = process.cwd()
let totalViolations = 0
const fileViolations = []
for (const searchDir of SEARCH_DIRS) {
const dir = join(cwd, searchDir)
for (const file of walk(dir)) {
const content = readFileSync(file, 'utf8')
const matches = content.match(HEX_PATTERN)
if (matches && matches.length > 0) {
totalViolations += matches.length
fileViolations.push({ file: file.replace(cwd + '\\', '').replace(cwd + '/', ''), count: matches.length })
}
}
}
fileViolations.sort((a, b) => b.count - a.count)
console.log('\n── Token Compliance Check ───────────────────────────────────────────')
console.log(`Total hex violations: ${totalViolations} (threshold: ${THRESHOLD})`)
console.log(`Status: ${totalViolations <= THRESHOLD ? '✅ PASS' : '❌ FAIL'}`)
console.log('\nTop 15 offenders:')
fileViolations.slice(0, 15).forEach(({ file, count }) => {
console.log(` ${count.toString().padStart(3)} ${file}`)
})
console.log('─────────────────────────────────────────────────────────────────────\n')
if (!reportOnly && totalViolations > THRESHOLD) {
console.error(`Error: ${totalViolations} hex violations exceed threshold of ${THRESHOLD}.`)
console.error('Run `node scripts/check-tokens.js --report` to see the full list.')
process.exit(1)
}