Auftragsbuch: Konzepte sichtbar, Modi unterscheidbar, Ideen weiterfuehrbar
Drei Luecken, die die neue Idee-Bahn unbrauchbar machten (User-Fund 21.07.):
1. Konzept unsichtbar. Der projektstart-Worker folgte dem Ende des
konzept-fliessband-Skills (Kopie nach ~/konzepte + Telegram) statt seiner
SOUL (Repo + KONZEPT.md) und schloss die Karte ab. Die Konzept-Ansicht sucht
nur in Gitea -> "noch kein Repo hinterlegt", obwohl das Konzept fertig war
(zweimal passiert: arm-ui, pomodoro-timer). konzept_of() faellt jetzt auf
~/konzepte/*.md zurueck (Pfad aus der Abschlussmeldung, sonst ueber den Titel
geraten; streng auf dieses Verzeichnis begrenzt) und nennt die Quelle.
Zusaetzlich sagen SOUL und Skill jetzt ausdruecklich, dass die Skill-Ablage
fuer Kanban-Worker NICHT das Ende ist.
2. Modus nicht erkennbar. Idee und IDE-Projekt trugen dasselbe "IDE"-Abzeichen,
Box-Karten gar keins. list_queue() liefert jetzt `art` (aus dem Body-Marker
AUFTRAGS-ART: IDEE PRUEFEN), die UI zeigt BOX / IDEE / IDE mit Erklaerung —
plus eine immer sichtbare Zeile, wann man welchen Modus nimmt.
3. Idee war eine Sackgasse. Neu: POST /api/ideen/weiterfuehren legt aus einer
fertig durchdachten Idee eine IDE-Projekt-Karte an — verkettet an die Idee,
mit dem fertigen Konzept als Vorlage ("denk es NICHT neu"), Titel aus der
Konzept-Ueberschrift statt aus dem Rohtext. Liegt das Konzept schon in einem
Repo, wird genau dieses weitergenutzt statt ein zweites anzulegen.
In der UI sitzt der Knopf unter dem gelesenen Konzept (Ziel + Aufwand).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -515,6 +515,7 @@ export interface IdeenItem {
|
||||
status: string // triage | todo | ready | running | blocked | review | done
|
||||
assignee: string | null
|
||||
welt?: string // "box" = Werkstatt baut · "ide" = für Zed vorbereitet
|
||||
art?: string // "box" | "idee" (nur durchdacht) | "projekt" (Repo vorbereitet)
|
||||
erstellt: number | null
|
||||
fertig: number | null
|
||||
von: string | null
|
||||
@@ -556,11 +557,13 @@ export interface IdeenErgebnis {
|
||||
result: string
|
||||
}
|
||||
|
||||
// Konzept eines vorbereiteten IDE-Projekts (KONZEPT.md aus dem Gitea-Repo)
|
||||
// Konzept einer IDE-Karte — aus dem Gitea-Repo oder aus ~/konzepte auf der Box
|
||||
export interface IdeenKonzept {
|
||||
available: boolean
|
||||
repo?: string | null
|
||||
clone_url?: string
|
||||
quelle?: "repo" | "datei"
|
||||
pfad?: string // nur quelle=datei: Pfad auf der Box
|
||||
datei?: string
|
||||
konzept?: string
|
||||
error?: string
|
||||
|
||||
@@ -152,6 +152,27 @@ const IDEEN_STATE: Record<string, { label: string; cls: string; spin?: boolean }
|
||||
done: { label: "fertig ✓", cls: "text-emerald-300 border-emerald-500/40 bg-emerald-500/10" },
|
||||
}
|
||||
|
||||
// Welche Welt hat die Karte durchlaufen? Vorher stand auf Idee UND IDE-Projekt dasselbe
|
||||
// „IDE" — man sah der fertigen Karte nicht an, ob sie nur durchdacht oder als Repo
|
||||
// vorbereitet wurde (User 21.07.). Jetzt drei ehrliche Marken.
|
||||
const ART_UI: Record<string, { label: string; cls: string; icon: typeof Hammer; title: string }> = {
|
||||
box: {
|
||||
label: "BOX", icon: Hammer,
|
||||
cls: "border-border/60 bg-background/40 text-muted-foreground",
|
||||
title: "Box-Welt: die Werkstatt baut das selbst. Das Ergebnis kommt als Patch-Vorschlag zurück — nichts geht ohne deinen Klick live.",
|
||||
},
|
||||
idee: {
|
||||
label: "IDEE", icon: Lightbulb,
|
||||
cls: "border-sky-500/40 bg-sky-500/10 text-sky-300",
|
||||
title: "Nur durchdacht: die Box hat die Produkt-Idee mit voller Denk-Kaskade zu einem Konzept ausgearbeitet. Kein Gerüst, kein Bau — du liest es und entscheidest, ob ein Projekt draus wird.",
|
||||
},
|
||||
projekt: {
|
||||
label: "IDE", icon: Code2,
|
||||
cls: "border-violet-500/40 bg-violet-500/10 text-violet-300",
|
||||
title: "IDE-Welt: die Box bereitet nur vor (Konzept + Repo + Doku, kein Code). Gebaut wird von dir in Zed.",
|
||||
},
|
||||
}
|
||||
|
||||
// Wie die Frage einer hängenden Karte eingeleitet wird — je nach Block-Grund des Workers.
|
||||
const FRAGE_HINT: Record<string, string> = {
|
||||
needs_input: "Die Box hat eine Frage:",
|
||||
@@ -281,12 +302,16 @@ function IdeenSection() {
|
||||
<span className="shrink-0 rounded bg-background/50 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground"
|
||||
title="Priorität — höher kommt zuerst dran (bei gleicher Prio: Erstellzeit)">P{it.prio}</span>
|
||||
)}
|
||||
{it.welt === "ide" && (
|
||||
<span className="flex shrink-0 items-center gap-1 rounded-lg border border-violet-500/40 bg-violet-500/10 px-2 py-0.5 text-[10px] font-bold text-violet-300"
|
||||
title="IDE-Welt: die Box bereitet nur vor (Konzept + Repo + Doku, kein Code). Gebaut wird von dir in Zed.">
|
||||
<Code2 className="h-3 w-3" /> IDE
|
||||
</span>
|
||||
)}
|
||||
{(() => {
|
||||
const a = ART_UI[it.art || (it.welt === "ide" ? "projekt" : "box")]
|
||||
if (!a) return null
|
||||
const Icon = a.icon
|
||||
return (
|
||||
<span className={cn("flex shrink-0 items-center gap-1 rounded-lg border px-2 py-0.5 text-[10px] font-bold", a.cls)} title={a.title}>
|
||||
<Icon className="h-3 w-3" /> {a.label}
|
||||
</span>
|
||||
)
|
||||
})()}
|
||||
<span className="min-w-0 flex-1 truncate text-xs text-foreground" title={it.body || it.titel}>{it.titel}</span>
|
||||
<span className="flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground/70"><Clock className="h-3 w-3" />{fmtWhen(it.erstellt)}</span>
|
||||
{wartend && (
|
||||
@@ -362,7 +387,13 @@ function IdeenSection() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{openKonzept === it.id && <KonzeptPanel id={it.id} />}
|
||||
{openKonzept === it.id && (
|
||||
<KonzeptPanel
|
||||
id={it.id}
|
||||
istIdee={it.art === "idee" && it.status === "done"}
|
||||
onWeiter={reload}
|
||||
/>
|
||||
)}
|
||||
|
||||
{it.status === "running" && it.notiz && (
|
||||
<p className="-mt-1 ml-1 text-[11px] italic leading-relaxed text-sky-200/80" title="Jüngste Zwischenmeldung des Workers (Heartbeat)">
|
||||
@@ -489,6 +520,16 @@ function IdeenSection() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Immer sichtbare Orientierung — der Text darunter erklärt nur den GEWÄHLTEN
|
||||
Modus, die Wahl selbst muss man aber vorher treffen können (User 21.07.). */}
|
||||
<p className="text-[10px] leading-relaxed text-muted-foreground/60">
|
||||
<b className="text-primary/80">Box</b>, wenn die Box es selbst bauen soll (dein Heimlabor,
|
||||
MC2, Lucy — Ergebnis kommt als Patch-Vorschlag zurück). ·{" "}
|
||||
<b className="text-sky-300/80">Idee</b>, wenn du noch nicht weißt, ob es das überhaupt wert
|
||||
ist — erst durchdenken, entscheiden kannst du danach. ·{" "}
|
||||
<b className="text-violet-300/80">IDE</b>, wenn du es selbst in Zed bauen willst und dafür
|
||||
ein startklares Repo brauchst.
|
||||
</p>
|
||||
{isProjekt && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[11px] text-muted-foreground/70">Ziel</span>
|
||||
@@ -628,10 +669,15 @@ function ProjektGruppe({ projekt, karten, zeile }: {
|
||||
)
|
||||
}
|
||||
|
||||
// Konzept eines vorbereiteten IDE-Projekts: holt KONZEPT.md aus dem Gitea-Repo und
|
||||
// rendert es direkt (User-Wunsch 20.07.: „lesen gehört in MC2"). Läuft die Vorbereitung
|
||||
// noch, gibt es evtl. noch kein Repo/Konzept — dann ehrlicher Hinweis + Nachlade-Button.
|
||||
function KonzeptPanel({ id }: { id: string }) {
|
||||
// Konzept einer IDE-Karte: holt es aus dem Gitea-Repo — oder, wenn der Worker keins
|
||||
// angelegt hat, aus seiner Datei in ~/konzepte (Bug-Fund 21.07.: dann stand hier vorher
|
||||
// „noch kein Repo hinterlegt", obwohl das Konzept längst fertig war).
|
||||
// Bei einer fertig durchdachten IDEE hängt unten der Weg nach vorn: „Gefällt mir".
|
||||
function KonzeptPanel({ id, istIdee, onWeiter }: {
|
||||
id: string
|
||||
istIdee?: boolean
|
||||
onWeiter?: () => void
|
||||
}) {
|
||||
const [data, setData] = useState<IdeenKonzept | null>(null)
|
||||
const [fehler, setFehler] = useState(false)
|
||||
const [versuch, setVersuch] = useState(0)
|
||||
@@ -656,6 +702,12 @@ function KonzeptPanel({ id }: { id: string }) {
|
||||
<GitFork className="h-3 w-3" /> {data.repo}
|
||||
</a>
|
||||
)}
|
||||
{!data?.repo && data?.quelle === "datei" && (
|
||||
<span className="truncate font-mono text-[10px] text-muted-foreground/70"
|
||||
title="Der Worker hat kein Repo angelegt — das Konzept liegt als Datei auf der Box.">
|
||||
{data.pfad}
|
||||
</span>
|
||||
)}
|
||||
<button onClick={() => setVersuch((v) => v + 1)} title="Neu laden"
|
||||
className="ml-auto rounded p-0.5 text-muted-foreground/50 hover:text-foreground cursor-pointer">
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
@@ -674,11 +726,88 @@ function KonzeptPanel({ id }: { id: string }) {
|
||||
{data.error || "Noch kein Konzept — das Projekt wird gerade vorbereitet."}
|
||||
</p>
|
||||
)}
|
||||
{istIdee && data?.konzept && <WeiterLeiste id={id} onFertig={onWeiter} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// „Gefällt mir" — aus der geprüften Idee ein IDE-Projekt machen. Ohne das war die Idee
|
||||
// eine Sackgasse (Titel neu eintippen, Konzept von vorn denken lassen). Die neue Karte
|
||||
// bekommt das fertige Konzept als Vorlage und hängt als Kette an der Idee.
|
||||
function WeiterLeiste({ id, onFertig }: { id: string; onFertig?: () => void }) {
|
||||
const [ziel, setZiel] = useState<"lxc" | "docker">("lxc")
|
||||
const [tiefe, setTiefe] = useState<"simpel" | "gruendlich">("simpel")
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [neu, setNeu] = useState<string | null>(null)
|
||||
const [fehler, setFehler] = useState("")
|
||||
const los = async () => {
|
||||
setBusy(true); setFehler("")
|
||||
try {
|
||||
const r = await api<{ id: string }>("/api/ideen/weiterfuehren", {
|
||||
method: "POST", body: JSON.stringify({ id, ziel, tiefe }),
|
||||
})
|
||||
setNeu(r.id)
|
||||
onFertig?.()
|
||||
} catch (e: any) {
|
||||
setFehler(String(e?.message || e))
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
if (neu) {
|
||||
return (
|
||||
<p className="mt-3 border-t border-violet-500/20 pt-2 text-[11px] text-emerald-300">
|
||||
Projekt-Karte <span className="font-mono">{neu}</span> ist angelegt — die Box bereitet das
|
||||
Repo vor (Konzept übernimmt sie, sie denkt es nicht neu). Danach öffnest du es in Zed.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="mt-3 space-y-2 border-t border-violet-500/20 pt-2.5">
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Gefällt dir das? Dann macht die Box daraus ein <b className="text-violet-300/90">startklares
|
||||
Repo</b> — Konzept wird übernommen (nicht neu gedacht), dazu Roadmap, AGENTS.md & Co.
|
||||
Gebaut wird danach von dir in Zed.
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<span className="text-[11px] text-muted-foreground/70">Ziel</span>
|
||||
<div className="flex rounded-lg border border-border/40 p-0.5">
|
||||
{(["lxc", "docker"] as const).map((z) => (
|
||||
<button key={z} onClick={() => setZiel(z)}
|
||||
title={z === "lxc" ? "Dienst in einem eigenen Proxmox-LXC (systemd, kein Docker) — wird automatisch mitgesichert."
|
||||
: "Natives Docker: Dockerfile + compose, portabel und pipeline-tauglich."}
|
||||
className={cn("rounded px-2 py-0.5 text-[10px] font-semibold transition-all cursor-pointer",
|
||||
ziel === z ? "bg-foreground/10 text-foreground" : "text-muted-foreground/50 hover:text-foreground/80")}>
|
||||
{z === "lxc" ? "LXC" : "Docker"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-[11px] text-muted-foreground/70">Aufwand</span>
|
||||
<div className="flex rounded-lg border border-border/40 p-0.5">
|
||||
{(["simpel", "gruendlich"] as const).map((t) => (
|
||||
<button key={t} onClick={() => setTiefe(t)}
|
||||
title={t === "simpel" ? "Konzept ist schon gehärtet — nur kurz auf die Bau-Sicht nachschärfen (empfohlen)."
|
||||
: "Nochmal volle Kaskade über die Bau-Sicht: mehrere Rollen, Härtetests. Dauert entsprechend."}
|
||||
className={cn("rounded px-2 py-0.5 text-[10px] font-semibold transition-all cursor-pointer",
|
||||
tiefe === t ? "bg-foreground/10 text-foreground" : "text-muted-foreground/50 hover:text-foreground/80")}>
|
||||
{t === "simpel" ? "schlank" : "gründlich"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
onClick={los}
|
||||
disabled={busy}
|
||||
className="ml-auto flex h-8 shrink-0 items-center gap-1.5 rounded-lg border border-violet-500/40 bg-violet-500/10 px-3 text-[10px] font-bold uppercase tracking-wide text-violet-300 transition-all hover:bg-violet-500/20 cursor-pointer disabled:opacity-50">
|
||||
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Code2 className="h-3.5 w-3.5" />}
|
||||
Gefällt mir — Projekt daraus machen
|
||||
</button>
|
||||
</div>
|
||||
{fehler && <p className="text-[11px] text-red-400">{fehler}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Ergebnis einer FERTIGEN Queue-Aufgabe: die Abschluss-Zusammenfassung des Workers
|
||||
// (kanban_complete). Lazy — wird erst beim Aufklappen geladen, ändert sich nie mehr.
|
||||
function ErgebnisPanel({ id }: { id: string }) {
|
||||
|
||||
Reference in New Issue
Block a user