Auftragsbuch: 'wartung/annahme-selbstheilung' angenommen (Ein-Klick-Gate)

This commit is contained in:
Auftragsbuch
2026-07-13 09:36:13 +02:00
12 changed files with 158 additions and 40 deletions
+15
View File
@@ -141,6 +141,10 @@ def _repo_items(repo: str, statuses: dict, gutachten: dict) -> list[dict]:
if ahead == 0 and ((status or {}).get("state") not in ("laeuft", "rollback")): if ahead == 0 and ((status or {}).get("state") not in ("laeuft", "rollback")):
continue # bereits in main enthalten → keine offene Entscheidung mehr continue # bereits in main enthalten → keine offene Entscheidung mehr
behind = int((_git(repo, ["rev-list", "--count", f"{ref}..origin/main"]).stdout or "0").strip() or 0) behind = int((_git(repo, ["rev-list", "--count", f"{ref}..origin/main"]).stdout or "0").strip() or 0)
# Kaputt aufgesetzte Branches entlarven (Worker machte git init/Shallow statt zu
# klonen): ohne gemeinsamen Vorfahren kann git NIE mergen — Annehmen wäre ein
# garantierter Fehllauf („refusing to merge unrelated histories").
verwaist = _git(repo, ["merge-base", "origin/main", ref]).returncode != 0
show = _git(repo, ["show", "-s", "--format=%s%x1f%b%x1f%ct%x1f%an", ref]) show = _git(repo, ["show", "-s", "--format=%s%x1f%b%x1f%ct%x1f%an", ref])
subject, body, cts, author = ((show.stdout or "").split("\x1f") + ["", "", "", ""])[:4] subject, body, cts, author = ((show.stdout or "").split("\x1f") + ["", "", "", ""])[:4]
ts_val = int(cts) if cts.strip().isdigit() else None ts_val = int(cts) if cts.strip().isdigit() else None
@@ -170,6 +174,11 @@ def _repo_items(repo: str, statuses: dict, gutachten: dict) -> list[dict]:
"empfehlung": stempel, "empfehlung": stempel,
"ahead": ahead, "ahead": ahead,
"behind": behind, "behind": behind,
"verwaist": verwaist,
# Diff gegen main ist leer → der Branch brächte nichts (Inhalt schon in main
# oder Worker hat am Repo vorbei gearbeitet). Bei verwaist ist der Diff
# technisch leer (kein merge-base) — dann zählt nur das verwaist-Flag.
"leer": not verwaist and ahead > 0 and not files_raw,
"shortstat": stat, "shortstat": stat,
"files": files, "files": files,
"files_truncated": len(files_raw) > 60, "files_truncated": len(files_raw) > 60,
@@ -227,6 +236,12 @@ def accept(branch: str, repo: str = "mc2") -> dict:
state = (_statuses().get(key) or {}).get("state") state = (_statuses().get(key) or {}).get("state")
if state in ("laeuft", "rollback"): if state in ("laeuft", "rollback"):
return {"ok": False, "error": "Für diesen Vorschlag läuft bereits ein Annahme-Lauf."} return {"ok": False, "error": "Für diesen Vorschlag läuft bereits ein Annahme-Lauf."}
# Kaputt aufgesetzter Branch (kein gemeinsamer Vorfahre) → Merge kann NIE gelingen;
# gar nicht erst einen Runner starten (die UI blendet den Knopf aus, die API hält dicht).
if _git(repo, ["merge-base", "origin/main", f"origin/{branch}"]).returncode != 0:
return {"ok": False, "error": "Dieser Branch hat keinen gemeinsamen Ursprung mit main "
"(kaputt aufgesetzt, z. B. git init statt Klonen) — Annehmen ist technisch unmöglich. "
"Bitte ablehnen (gern mit Grund) und die Idee neu in die Queue geben."}
# Runner als /tmp-Kopie starten: deploy.sh resettet das Repo hart — das Original-Skript # Runner als /tmp-Kopie starten: deploy.sh resettet das Repo hart — das Original-Skript
# würde einem laufenden bash unter den Füßen getauscht (bekannte Selbst-Reset-Falle). # würde einem laufenden bash unter den Füßen getauscht (bekannte Selbst-Reset-Falle).
+25 -2
View File
@@ -57,13 +57,36 @@ git rev-parse --verify -q "refs/remotes/origin/$BRANCH" >/dev/null \
PRE="$(git rev-parse origin/main)" PRE="$(git rev-parse origin/main)"
# Kaputt aufgesetzte Branches (Worker machte git init/Shallow statt zu klonen) haben KEINEN
# gemeinsamen Vorfahren — git verweigert den Merge immer. Ehrlich sagen statt „Konflikt".
git merge-base origin/main "origin/$BRANCH" >/dev/null 2>&1 \
|| fail "Branch hat keinen gemeinsamen Ursprung mit main (kaputt aufgesetzt, z. B. git init statt Klonen) — bitte ablehnen (mit Grund) und die Idee neu in die Queue geben"
# Isolierter Worktree — der Live-Checkout bleibt bis zum Deploy unberührt. # Isolierter Worktree — der Live-Checkout bleibt bis zum Deploy unberührt.
cleanup_wt cleanup_wt
git worktree add --detach "$WT" origin/main >/dev/null 2>&1 || fail "Worktree konnte nicht angelegt werden" git worktree add --detach "$WT" origin/main >/dev/null 2>&1 || fail "Worktree konnte nicht angelegt werden"
MERGE_MSG="Auftragsbuch: '$BRANCH' angenommen (Ein-Klick-Gate)"
if ! git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \ if ! git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
merge --no-ff "origin/$BRANCH" -m "Auftragsbuch: '$BRANCH' angenommen (Ein-Klick-Gate)" >>"$LOG" 2>&1; then merge --no-ff "origin/$BRANCH" -m "$MERGE_MSG" >>"$LOG" 2>&1; then
git -C "$WT" merge --abort 2>/dev/null || true git -C "$WT" merge --abort 2>/dev/null || true
fail "Merge-Konflikt mit main — der Branch ist veraltet, bitte am PC auflösen" # Selbstheilung: veralteten Branch mechanisch auf main rebasen (zweiter Worktree am
# Branch-Kopf). Klappt das sauber, wird der rebasede Stand gemergt — kein PC nötig.
status "laeuft" "Merge-Konflikt — Auto-Rebase wird versucht"
WT2="/tmp/annahme-rebase-$SLUG"
git worktree remove --force "$WT2" 2>/dev/null || true
REBASED=""
if git worktree add --detach "$WT2" "origin/$BRANCH" >/dev/null 2>&1 \
&& git -C "$WT2" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
rebase origin/main >>"$LOG" 2>&1; then
REBASED="$(git -C "$WT2" rev-parse HEAD)"
else
git -C "$WT2" rebase --abort 2>/dev/null || true
fi
git worktree remove --force "$WT2" 2>/dev/null || true
[ -n "$REBASED" ] || fail "Merge-Konflikt mit main, Auto-Rebase scheiterte ebenfalls — echter Inhaltskonflikt; am einfachsten ablehnen und die Idee neu in die Queue geben (frischer Branch von aktuellem main)"
git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
merge --no-ff "$REBASED" -m "$MERGE_MSG — auto-rebased (Branch war veraltet)" >>"$LOG" 2>&1 \
|| { git -C "$WT" merge --abort 2>/dev/null || true; fail "Merge nach Auto-Rebase fehlgeschlagen (unerwartet) — Log: $LOG"; }
fi fi
# py_compile-Gate über die durch den Merge geänderten Python-Dateien (AGENTS.md-Regel). # py_compile-Gate über die durch den Merge geänderten Python-Dateien (AGENTS.md-Regel).
+23 -2
View File
@@ -130,12 +130,33 @@ git -C "$LUCY" fetch -q origin || fail "git fetch (Gitea) fehlgeschlagen"
git -C "$LUCY" rev-parse --verify -q "refs/remotes/origin/$BRANCH" >/dev/null \ git -C "$LUCY" rev-parse --verify -q "refs/remotes/origin/$BRANCH" >/dev/null \
|| fail "Branch origin/$BRANCH existiert nicht (schon gemergt/gelöscht?)" || fail "Branch origin/$BRANCH existiert nicht (schon gemergt/gelöscht?)"
# Kaputt aufgesetzte Branches (kein gemeinsamer Vorfahre) → Merge kann NIE gelingen.
git -C "$LUCY" merge-base origin/main "origin/$BRANCH" >/dev/null 2>&1 \
|| fail "Branch hat keinen gemeinsamen Ursprung mit main (kaputt aufgesetzt, z. B. git init statt Klonen) — bitte ablehnen (mit Grund) und die Idee neu in die Queue geben"
cleanup_wt cleanup_wt
git -C "$LUCY" worktree add --detach "$WT" origin/main >/dev/null 2>&1 || fail "Worktree konnte nicht angelegt werden" git -C "$LUCY" worktree add --detach "$WT" origin/main >/dev/null 2>&1 || fail "Worktree konnte nicht angelegt werden"
MERGE_MSG="Auftragsbuch: Lucy-Vorschlag '$BRANCH' angenommen (Ein-Klick-Gate)"
if ! git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \ if ! git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
merge --no-ff "origin/$BRANCH" -m "Auftragsbuch: Lucy-Vorschlag '$BRANCH' angenommen (Ein-Klick-Gate)" >>"$LOG" 2>&1; then merge --no-ff "origin/$BRANCH" -m "$MERGE_MSG" >>"$LOG" 2>&1; then
git -C "$WT" merge --abort 2>/dev/null || true git -C "$WT" merge --abort 2>/dev/null || true
fail "Merge-Konflikt mit main — der Branch ist veraltet, bitte am PC auflösen" # Selbstheilung: veralteten Branch mechanisch auf main rebasen (wie im MC2-Runner).
status "laeuft" "Merge-Konflikt — Auto-Rebase wird versucht"
WT2="/tmp/annahme-rebase-$SLUG"
git -C "$LUCY" worktree remove --force "$WT2" 2>/dev/null || true
REBASED=""
if git -C "$LUCY" worktree add --detach "$WT2" "origin/$BRANCH" >/dev/null 2>&1 \
&& git -C "$WT2" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
rebase origin/main >>"$LOG" 2>&1; then
REBASED="$(git -C "$WT2" rev-parse HEAD)"
else
git -C "$WT2" rebase --abort 2>/dev/null || true
fi
git -C "$LUCY" worktree remove --force "$WT2" 2>/dev/null || true
[ -n "$REBASED" ] || fail "Merge-Konflikt mit main, Auto-Rebase scheiterte ebenfalls — echter Inhaltskonflikt; am einfachsten ablehnen und die Idee neu in die Queue geben (frischer Branch von aktuellem main)"
git -C "$WT" -c user.name="Auftragsbuch" -c user.email="auftragsbuch@box.local" \
merge --no-ff "$REBASED" -m "$MERGE_MSG — auto-rebased (Branch war veraltet)" >>"$LOG" 2>&1 \
|| { git -C "$WT" merge --abort 2>/dev/null || true; fail "Merge nach Auto-Rebase fehlgeschlagen (unerwartet) — Log: $LOG"; }
fi fi
status "laeuft" "Push nach main läuft" status "laeuft" "Push nach main läuft"
+10 -1
View File
@@ -11,10 +11,19 @@ sauberes Handwerk.
2. Ergebnis eines Code-Auftrags ist IMMER ein Vorschlags-Branch auf Gitea — NIEMALS Push 2. Ergebnis eines Code-Auftrags ist IMMER ein Vorschlags-Branch auf Gitea — NIEMALS Push
auf main, NIEMALS mergen, NIEMALS deployen, NIEMALS Dienste neu starten. Der Commander auf main, NIEMALS mergen, NIEMALS deployen, NIEMALS Dienste neu starten. Der Commander
klickt die Karte im Auftragsbuch. klickt die Karte im Auftragsbuch.
3. Frisch klonen statt Live-Checkout nutzen: 3. Frisch und VOLL klonen statt Live-Checkout nutzen:
`git clone https://git.tobisniceshomelab.ddnsfree.com/Hitonabi/mission-control-v2` `git clone https://git.tobisniceshomelab.ddnsfree.com/Hitonabi/mission-control-v2`
(Credentials liegen in `~/.git-credentials`; Lucy-Repo: `.../Hitonabi/lucy`). (Credentials liegen in `~/.git-credentials`; Lucy-Repo: `.../Hitonabi/lucy`).
NIEMALS `git init`, NIEMALS `--depth`/Shallow, NIEMALS in einem Verzeichnis committen,
das nicht dieser frische Klon ist — sonst entsteht ein Branch OHNE gemeinsamen
Ursprung, den das Auftragsbuch als „kaputt aufgesetzt" aussortiert (Vorfall
12.07.2026: feature/cleanse-skill-index war ein git-init-Orphan mit ~/.hermes-Dateien).
Branch-Namen: `wartung/<kurz>`, `feature/<kurz>` oder `doku/<kurz>`. Branch-Namen: `wartung/<kurz>`, `feature/<kurz>` oder `doku/<kurz>`.
SELBSTCHECK vor dem Push (Pflicht, beide Zeilen müssen klappen):
`git merge-base HEAD origin/main` (muss einen Hash liefern) und
`git fetch origin && git rebase origin/main` (veraltete Basis sofort begradigen;
Konflikt → `kanban_block`, nicht raten). Committe NUR Dateien, die zum Repo gehören —
`~/.hermes`-Artefakte (.usage.json, .bundled_manifest, Skill-Indizes) gehören NIE hinein.
4. Gate vor dem Push: `python3 -m py_compile` für JEDE geänderte .py-Datei. Frontend 4. Gate vor dem Push: `python3 -m py_compile` für JEDE geänderte .py-Datei. Frontend
(`frontend/`) nur anfassen, wenn der Auftrag es verlangt — die Box kann kein (`frontend/`) nur anfassen, wenn der Auftrag es verlangt — die Box kann kein
`npm build`; das ehrlich in Commit-Text und Summary sagen. `npm build`; das ehrlich in Commit-Text und Summary sagen.
+12
View File
@@ -53,6 +53,9 @@ bewiesen; ein Eigenbau hätte all das nur nachgebaut.
## Was „Annehmen" bei einem Patch macht (deploy/auftrag-annehmen.sh) ## Was „Annehmen" bei einem Patch macht (deploy/auftrag-annehmen.sh)
1. Merge des Branches in einem **isolierten Worktree** (Live-Checkout bleibt unberührt). 1. Merge des Branches in einem **isolierten Worktree** (Live-Checkout bleibt unberührt).
Kollidiert der Merge, weil main weitergelaufen ist, versucht der Runner **automatisch
einen Rebase** des Branches auf main (Selbstheilung für veraltete Vorschläge) — erst
wenn auch der kollidiert, fällt die Karte mit ehrlicher Meldung durch.
2. `py_compile`-Gate über die geänderten Python-Dateien. 2. `py_compile`-Gate über die geänderten Python-Dateien.
3. Push nach `main` auf Gitea (mit Retry). 3. Push nach `main` auf Gitea (mit Retry).
4. Deploy (als /tmp-Kopie von deploy.sh — Selbst-Reset-Falle) — die Zentrale 4. Deploy (als /tmp-Kopie von deploy.sh — Selbst-Reset-Falle) — die Zentrale
@@ -64,6 +67,15 @@ Der Lauf ist eine eigene systemd-Unit (detached) — er überlebt den Neustart d
Backends, das ihn gestartet hat. Fortschritt steht live auf der Karte Backends, das ihn gestartet hat. Fortschritt steht live auf der Karte
(`/srv/models/mc2-auftragsbuch.json`). (`/srv/models/mc2-auftragsbuch.json`).
**Kaputt aufgesetzte Branches** (Worker machte `git init`/Shallow statt zu klonen → kein
gemeinsamer Ursprung mit main, „refusing to merge unrelated histories") erkennt das
Auftragsbuch an der Quelle: rote Markierung „kein gemeinsamer Ursprung", der
Annehmen-Knopf fehlt (Annehmen wäre ein garantierter Fehllauf), API und Runner halten
zusätzlich dicht. Ebenso markiert: „Diff leer — bringt nichts" (Branch würde gegenüber
main nichts ändern). In beiden Fällen: **ablehnen mit Grund** und die Idee frisch in die
Queue geben; die Prävention (voll klonen, merge-base-Selbstcheck, fetch+rebase vor dem
Push) steht in der werkstatt-SOUL.
## Was „Annehmen" bei einer LUCY-Karte macht (deploy/lucy-annahme.sh) ## Was „Annehmen" bei einer LUCY-Karte macht (deploy/lucy-annahme.sh)
Lucy (Electron) lebt auf dem Windows-PC; ihr `dist` liegt nicht im Repo — die Box Lucy (Electron) lebt auf dem Windows-PC; ihr `dist` liegt nicht im Repo — die Box
+11
View File
@@ -26,6 +26,17 @@ Neue Fallen: hier eintragen, mit Datum und Symptom._
CR-Falle (`sed 's/\r$//'`). CR-Falle (`sed 's/\r$//'`).
- **Stale `index.lock`** im Box-Repo nach Deploy-Abbruch: Alter prüfen, dann löschen. - **Stale `index.lock`** im Box-Repo nach Deploy-Abbruch: Alter prüfen, dann löschen.
- **`hermes` ist über SSH nicht im PATH** → immer `bash -lc 'hermes …'`. - **`hermes` ist über SSH nicht im PATH** → immer `bash -lc 'hermes …'`.
- **Werkstatt-Orphan-Branches („refusing to merge unrelated histories"):** macht ein
Worker `git init`/Shallow statt voll zu klonen, entsteht ein Branch OHNE gemeinsamen
Vorfahren — Erkennungsmuster auf der Karte: **Diff „0 Dateien" + „hinter main" ≈ ganze
Historie** (z. B. 386). Merge ist NIE möglich; oft ist der Inhalt obendrein Müll
(~/.hermes-Dateien statt Repo-Dateien). Seit 12.07.2026 markiert das Auftragsbuch solche
Karten („kein gemeinsamer Ursprung", Annehmen-Knopf weg) → ablehnen mit Grund, Idee neu
in die Queue. Prävention steht in der werkstatt-SOUL (voll klonen + merge-base-Selbstcheck).
- **Ehrlich-veraltete Branches (echter Merge-Konflikt):** die Annahme versucht seit
12.07.2026 automatisch einen Rebase auf main (isolierter Worktree, Gates laufen danach
normal). Nur wenn AUCH der Rebase kollidiert, fällt die Karte durch — dann ablehnen und
die Idee frisch einqueuen statt „am PC auflösen".
## Box / systemd / llama-swap ## Box / systemd / llama-swap
+7 -2
View File
@@ -29,8 +29,13 @@ keine zweite Liste anlegen. Verdikte werden NICHT als „offen" geführt → [VE
durch: A4-Turncheck Default AUS (680 Entscheidungen, 79 % der Halte umsonst), dist am PC durch: A4-Turncheck Default AUS (680 Entscheidungen, 79 % der Halte umsonst), dist am PC
gebaut. **Erster Live-Lauf fand 2 Runner-Bugs** (Start-Process-Quoting bei Leerzeichen-Pfad, gebaut. **Erster Live-Lauf fand 2 Runner-Bugs** (Start-Process-Quoting bei Leerzeichen-Pfad,
f-String-`\"` auf Python 3.14 → Poll blind; Abschluss manuell nachgezogen, Build war grün) — f-String-`\"` auf Python 3.14 → Poll blind; Abschluss manuell nachgezogen, Build war grün) —
Fixes + Executor-Fensterblitz-Fix in Karte `wartung/lucy-annahme-fixes`. Details FALLEN.md. Fixes + Executor-Fensterblitz-Fix in Karte `wartung/lucy-annahme-fixes` (✅ angenommen).
**Der nächste Lucy-Auftrag ist der ehrliche Voll-Automatik-Test.** Details FALLEN.md. **Der nächste Lucy-Auftrag ist der ehrliche Voll-Automatik-Test.**
- **Annahme-Selbstheilung (12.07. abends, nach cleanse-skill-index-Vorfall):** Auftragsbuch
erkennt Orphan-Branches („kein gemeinsamer Ursprung", Annehmen-Knopf weg) und leere
Diffs; beide Runner rebasen veraltete Branches automatisch, bevor sie aufgeben;
werkstatt-SOUL: voll klonen + merge-base-Selbstcheck + fetch/rebase vor Push, nie
~/.hermes-Artefakte committen. Karte `wartung/annahme-selbstheilung`.
- **S4 „leg los zuendung" ✅ gebaut (12.07.):** Idle-Radar (`deploy/idle-radar-feed.sh`, - **S4 „leg los zuendung" ✅ gebaut (12.07.):** Idle-Radar (`deploy/idle-radar-feed.sh`,
Hermes-Cron nächtlich 03:45): Journal-Fehlermuster + Traum-Funde → max. 2 belegte rohe Hermes-Cron nächtlich 03:45): Journal-Fehlermuster + Traum-Funde → max. 2 belegte rohe
Ideen/Nacht ins native Kanban (Ehrlichkeits-Gate prüft Beleg-Zitate mechanisch, Ideen/Nacht ins native Kanban (Ehrlichkeits-Gate prüft Beleg-Zitate mechanisch,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title> <title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-D-87Dv5s.js"></script> <script type="module" crossorigin src="/assets/index-DyMSyf_4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cq3xDDcK.css"> <link rel="stylesheet" crossorigin href="/assets/index-Cq3xDDcK.css">
</head> </head>
<body> <body>
+2
View File
@@ -461,6 +461,8 @@ export interface AuftragItem {
files: { status: string; path: string }[] files: { status: string; path: string }[]
files_truncated: boolean files_truncated: boolean
frontend_ohne_build: boolean // Frontend-Quelltext ohne dist → Box kann nicht bauen frontend_ohne_build: boolean // Frontend-Quelltext ohne dist → Box kann nicht bauen
verwaist?: boolean // kein gemeinsamer Ursprung mit main (kaputt aufgesetzt) → Merge unmöglich
leer?: boolean // Diff gegen main ist leer → bringt nichts (schon in main / am Repo vorbei)
status: AuftragStatus | null status: AuftragStatus | null
// Empfehlungs-Stempel des nächtlichen Karten-Gutachters (Meinung, kein Gate). // Empfehlungs-Stempel des nächtlichen Karten-Gutachters (Meinung, kein Gate).
empfehlung: { empfehlung: "ANNEHMEN" | "ABLEHNEN" | "UNKLAR"; satz: string; richter: string } | null empfehlung: { empfehlung: "ANNEHMEN" | "ABLEHNEN" | "UNKLAR"; satz: string; richter: string } | null
+22 -2
View File
@@ -344,6 +344,7 @@ function ProposalCard({
{!running && !decided && ( {!running && !decided && (
<div className="flex shrink-0 items-center gap-2"> <div className="flex shrink-0 items-center gap-2">
{!item.verwaist && (
<button <button
disabled={!!busy[k]} disabled={!!busy[k]}
onClick={() => onClick={() =>
@@ -355,6 +356,7 @@ function ProposalCard({
className="flex h-9 items-center gap-1.5 rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-emerald-300 transition-all hover:bg-emerald-500/20 cursor-pointer disabled:opacity-50"> className="flex h-9 items-center gap-1.5 rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-emerald-300 transition-all hover:bg-emerald-500/20 cursor-pointer disabled:opacity-50">
{busy[k] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />} Annehmen {busy[k] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />} Annehmen
</button> </button>
)}
<button <button
disabled={!!busy["ab:" + k]} disabled={!!busy["ab:" + k]}
onClick={() => setRejecting((v) => !v)} onClick={() => setRejecting((v) => !v)}
@@ -401,11 +403,29 @@ function ProposalCard({
<p className="text-[11px] text-muted-foreground">{item.status.detail}</p> <p className="text-[11px] text-muted-foreground">{item.status.detail}</p>
)} )}
{item.verwaist && (
<p className="rounded-lg border border-red-500/30 bg-red-500/5 px-2 py-1.5 text-[11px] text-red-300/90">
<b>Kaputt aufgesetzt:</b> Dieser Branch hat keinen gemeinsamen Ursprung mit main (der Werkstatt-Worker
hat nicht richtig geklont) Annehmen ist technisch unmöglich, deshalb fehlt der Knopf.
Am besten <b>ablehnen mit Grund</b>; die Idee kann danach frisch in die Queue.
</p>
)}
<div className="flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground"> <div className="flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground">
<span className="rounded bg-background/40 px-1.5 py-0.5 font-mono">{item.ahead} Commit{item.ahead === 1 ? "" : "s"}</span> <span className="rounded bg-background/40 px-1.5 py-0.5 font-mono">{item.ahead} Commit{item.ahead === 1 ? "" : "s"}</span>
{item.shortstat && <span className="rounded bg-background/40 px-1.5 py-0.5 font-mono">{item.shortstat.trim()}</span>} {item.shortstat && <span className="rounded bg-background/40 px-1.5 py-0.5 font-mono">{item.shortstat.trim()}</span>}
{item.behind > 0 && ( {item.verwaist && (
<span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="main ist seit diesem Vorschlag weitergelaufen"> <span className="flex items-center gap-1 rounded bg-red-500/10 px-1.5 py-0.5 text-red-300" title="Kein gemeinsamer Vorfahre mit main — git kann diesen Branch niemals mergen.">
<AlertTriangle className="h-3 w-3" /> kein gemeinsamer Ursprung
</span>
)}
{item.leer && (
<span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="Der Diff gegen main ist leer — der Branch würde nichts verändern (Inhalt schon in main oder am Repo vorbei gearbeitet).">
<AlertTriangle className="h-3 w-3" /> Diff leer bringt nichts
</span>
)}
{item.behind > 0 && !item.verwaist && (
<span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="main ist seit diesem Vorschlag weitergelaufen — bei Konflikten versucht die Annahme automatisch einen Rebase.">
<AlertTriangle className="h-3 w-3" /> {item.behind} hinter main <AlertTriangle className="h-3 w-3" /> {item.behind} hinter main
</span> </span>
)} )}