Auftragsbuch ausgebaut: Karten-Annahme, Bagatell-Annahme, Lucy-Annahme raus — Gate ist Ampel + User-Merge
Ampel / ampel (push) Failing after 24s
Ampel / ampel (push) Failing after 24s
Die Vorschlags-Inbox (routers/services auftragsbuch, AuftragsbuchView, /auftraege, Cockpit- Kachel + Handlungsbedarf-Eintrag, deploy/auftrag-annehmen.sh, lucy-annahme.sh, bagatell- annahme.sh, docs/AUFTRAGSBUCH.md) war seit 02.08.2026 ungenutzt: Kanban-Tools seit 21.08. aus, v3-Umbau lief ueber Branch -> Ampel -> Merge -> deploy.sh, PC-Executor-IP in Box und Config veraltet. Doku behauptete den Karten-Weg trotzdem als Standard. Jetzt: - Cockpit: Kachel "Ideen" (offen/haengend) statt "Auftragsbuch"; Handlungsbedarf zeigt haengende Ideen und springt in die Ideen-Ansicht. - Guide/Schaubild: Kreislauf Idee -> Ideen-Queue -> IDE am PC -> Ampel -> DEIN Merge -> Live. - events.py: kein Auftragsbuch-Fingerprint mehr; config.py: PC_EXECUTOR_URL-Default auf .22. - Skills/Skripte: selbst-inventur ohne Karten-Abschnitt, morning-report nennt offene Branches, konzept-fliessband verweist auf die Ideen-Ansicht. - Doku: STACK "Deploy & Pipeline" = Branch -> Ampel -> User-Merge -> deploy.sh (einziger Weg), GRENZEN/ARBEITSWEISE/FALLEN/README/BEDIENUNG/RUNBOOK/GEMINI_BRIEFING/gitea-workflow angepasst, ZIELBILD Punkt 9, OFFENE-FAEDEN + RAPHAEL.md: erledigt. - Bewusst geblieben: werkstatt-SOUL/projektstart-SOUL (Werkstatt-Persona, eigener Faden), Chronik-Kategorie "auftragsbuch" fuer historische Eintraege. Auf der Box bereits erledigt (Hand): mc2-bagatell.timer deaktiviert + Units archiviert, PC-Executor-URL in ~/.hermes/config.yaml auf .22. Baut auf wartung/lucy-stimme-proxy-raphael auf (Proxy /api/lucy/stimme/*). Gates: eslint 0 Fehler, vitest 59/59, tsc + vite build gruen, frontend/dist committet, py_compile gruen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
df8d088fac
commit
479fecd2c6
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as B,M as T,j as e,N as p,W as g,b as n,J as D,T as G,z as L,O as $,f as R,k as F,u as I,r as q,P as y,Q as v,t as w,X as U,C as k,n as _,q as u}from"./index-l6kyKCL9.js";import{S}from"./SectionLabel-zEOM9wM3.js";import{C as K}from"./clock-QfyF1ur_.js";import{E as O}from"./external-link-ClFd8iN1.js";import{S as Q}from"./shield-CVm2T_f5.js";/**
|
||||
import{c as B,M as T,j as e,N as p,W as g,b as n,J as D,T as G,z as L,O as $,f as R,k as F,u as I,r as q,P as y,Q as v,t as w,X as U,C as k,n as _,q as u}from"./index-DMY9lUCb.js";import{S}from"./SectionLabel-B7ySrBs7.js";import{C as K}from"./clock-Bth-s5Y3.js";import{E as O}from"./external-link-DcJao8h4.js";import{S as Q}from"./shield-BtE9Fxv6.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
File diff suppressed because one or more lines are too long
Vendored
+12
-7
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as It,r as a,j as E,at as be,W as In,au as An,$ as _n,av as Mn,aw as ht,z as Tn,n as Ln,T as Fn,b as jn}from"./index-l6kyKCL9.js";import{r as At}from"./index-GVsa8kqq.js";import{C as $n,a as Wn}from"./cloud-download-YEWUSwEY.js";import{R as Un}from"./refresh-cw-BV1qZMSR.js";import{S as Bn}from"./search-C1Zs6OOf.js";import{F as Vn}from"./file-text-nO0jfgun.js";/**
|
||||
import{c as It,r as a,j as E,ar as be,W as In,as as An,$ as _n,at as Mn,au as ht,z as Tn,n as Ln,T as Fn,b as jn}from"./index-DMY9lUCb.js";import{r as At}from"./index-CVfMhtws.js";import{C as $n,a as Wn}from"./cloud-download-BGirmHxu.js";import{R as Un}from"./refresh-cw-hAcZthO8.js";import{S as Bn}from"./search-D_8DnBvS.js";import{F as Vn}from"./file-text-BSQuFPZM.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as f,r as m,D as G,F as H,j as e,t as I,B as M,I as y,G as z,b as S,L as B,J as _,K as E,C as P}from"./index-l6kyKCL9.js";import{A as u}from"./arrow-right-DSSXd-8y.js";import{C as K}from"./chevron-down-C5keVSfm.js";/**
|
||||
import{c as f,r as m,D as G,F as H,j as e,t as I,B as M,I as y,G as z,b as S,L as B,J as _,K as E,C as P}from"./index-DMY9lUCb.js";import{A as u}from"./arrow-right-CNrCtUoh.js";import{C as K}from"./chevron-down-1Vx9W9jM.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+36
File diff suppressed because one or more lines are too long
-36
File diff suppressed because one or more lines are too long
Vendored
+15
-10
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
import{u,ag as m,k as x,j as s,y as n,ah as b,b as p,n as f,q as h}from"./index-l6kyKCL9.js";function y(){const d=u(),{data:t=[]}=m(),{showAlert:l,dialogElement:o}=x();async function i(e){try{await f(`/api/jobs/${e}/cancel`,{method:"POST"}),d.invalidateQueries({queryKey:h.jobs})}catch(c){l("Fehler",c.message)}}const a=t.filter(e=>e.state==="running"||e.state==="queued"),r=t.filter(e=>e.state!=="running"&&e.state!=="queued").slice(-3);return a.length===0&&r.length===0?null:s.jsxs("div",{className:"space-y-3 mc-card p-4",children:[s.jsx("div",{className:"text-[11px] font-semibold text-muted-foreground uppercase tracking-wider",children:"Aktive Downloads"}),a.map(e=>s.jsxs("div",{className:"space-y-1.5 p-3 rounded-xl bg-background/20 border border-border/40",children:[s.jsxs("div",{className:"flex justify-between items-center text-xs",children:[s.jsx("span",{className:"font-semibold truncate max-w-[250px]",children:e.label}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsxs("span",{className:"text-muted-foreground font-mono",children:[e.progress??0,"% • ",n(e.done_bytes),"/",n(e.total_bytes),e.eta_s?` • ETA ${b(e.eta_s)}`:""]}),s.jsx("button",{onClick:()=>i(e.id),className:"text-[10px] text-red-400 hover:text-red-300 font-semibold border border-red-500/25 bg-red-500/5 px-2 py-0.5 rounded transition-all cursor-pointer",children:"Abbrechen"})]})]}),s.jsx("div",{className:"h-1.5 overflow-hidden rounded-full bg-muted",children:s.jsx("div",{className:"h-full rounded-full bg-primary transition-all duration-500",style:{width:`${e.progress??0}%`}})})]},e.id)),r.map(e=>s.jsxs("div",{className:"flex justify-between items-center text-xs text-muted-foreground px-1",children:[s.jsx("span",{className:"truncate",children:e.label}),s.jsx("span",{className:p("font-semibold text-[10px] px-1.5 py-0.5 rounded uppercase font-mono",e.state==="done"?"bg-emerald-500/10 text-emerald-400":"bg-amber-500/10 text-amber-400"),children:e.state})]},e.id)),o]})}export{y as J};
|
||||
import{u,ae as m,k as x,j as s,y as n,af as b,b as p,n as f,q as h}from"./index-DMY9lUCb.js";function y(){const d=u(),{data:t=[]}=m(),{showAlert:l,dialogElement:o}=x();async function i(e){try{await f(`/api/jobs/${e}/cancel`,{method:"POST"}),d.invalidateQueries({queryKey:h.jobs})}catch(c){l("Fehler",c.message)}}const a=t.filter(e=>e.state==="running"||e.state==="queued"),r=t.filter(e=>e.state!=="running"&&e.state!=="queued").slice(-3);return a.length===0&&r.length===0?null:s.jsxs("div",{className:"space-y-3 mc-card p-4",children:[s.jsx("div",{className:"text-[11px] font-semibold text-muted-foreground uppercase tracking-wider",children:"Aktive Downloads"}),a.map(e=>s.jsxs("div",{className:"space-y-1.5 p-3 rounded-xl bg-background/20 border border-border/40",children:[s.jsxs("div",{className:"flex justify-between items-center text-xs",children:[s.jsx("span",{className:"font-semibold truncate max-w-[250px]",children:e.label}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsxs("span",{className:"text-muted-foreground font-mono",children:[e.progress??0,"% • ",n(e.done_bytes),"/",n(e.total_bytes),e.eta_s?` • ETA ${b(e.eta_s)}`:""]}),s.jsx("button",{onClick:()=>i(e.id),className:"text-[10px] text-red-400 hover:text-red-300 font-semibold border border-red-500/25 bg-red-500/5 px-2 py-0.5 rounded transition-all cursor-pointer",children:"Abbrechen"})]})]}),s.jsx("div",{className:"h-1.5 overflow-hidden rounded-full bg-muted",children:s.jsx("div",{className:"h-full rounded-full bg-primary transition-all duration-500",style:{width:`${e.progress??0}%`}})})]},e.id)),r.map(e=>s.jsxs("div",{className:"flex justify-between items-center text-xs text-muted-foreground px-1",children:[s.jsx("span",{className:"truncate",children:e.label}),s.jsx("span",{className:p("font-semibold text-[10px] px-1.5 py-0.5 rounded uppercase font-mono",e.state==="done"?"bg-emerald-500/10 text-emerald-400":"bg-amber-500/10 text-amber-400"),children:e.state})]},e.id)),o]})}export{y as J};
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{O as h,u as g,P as p,r as d,j as e,b as x,T as j,S as N,n as v,U as m,V as w,q as f}from"./index-l6kyKCL9.js";import{E as k}from"./external-link-ClFd8iN1.js";import{R as y}from"./refresh-cw-BV1qZMSR.js";function K(){const{data:t}=h(),u=g(),a=t!=null&&t.box_console_url?p(t.box_console_url):void 0,n=t==null?void 0:t.box_console_reachable,[o,i]=d.useState(!1),[c,l]=d.useState("");async function b(){i(!0),l("");try{const s=await v("/api/maintenance/restart",{method:"POST",body:JSON.stringify({service:"box-console"})}),r=s.ok?"Konsolen-Dienst neu gestartet — einen Moment, dann lädt das Terminal.":`Neustart fehlgeschlagen: ${s.err||"Unbekannter Fehler"}`;l(r),m(s.ok?"erfolg":"fehler",r),w(u,f.agentStatus,f.services)}catch(s){const r=`Neustart fehlgeschlagen: ${(s==null?void 0:s.message)||s}`;l(r),m("fehler",r)}finally{i(!1)}}return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:x("h-2 w-2 rounded-full",n?"bg-emerald-500 animate-pulse":"bg-amber-500")}),n?"online":"offline"]}),a&&e.jsxs("a",{href:a,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(k,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),a?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[n===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(j,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der Terminal-Dienst (",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),") läuft gerade nicht."]}),e.jsxs("button",{onClick:b,disabled:o,className:"flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50",children:[e.jsx(y,{className:x("h-3.5 w-3.5",o&&"animate-spin")})," Dienst neu starten"]}),c&&e.jsx("p",{className:"max-w-sm text-[11px] text-muted-foreground",children:c})]}),e.jsx("iframe",{src:a,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(N,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{K as KonsoleView};
|
||||
import{O as h,u as g,P as p,r as d,j as e,b as x,T as j,S as N,n as v,U as m,V as w,q as f}from"./index-DMY9lUCb.js";import{E as k}from"./external-link-DcJao8h4.js";import{R as y}from"./refresh-cw-hAcZthO8.js";function K(){const{data:t}=h(),u=g(),a=t!=null&&t.box_console_url?p(t.box_console_url):void 0,n=t==null?void 0:t.box_console_reachable,[o,i]=d.useState(!1),[c,l]=d.useState("");async function b(){i(!0),l("");try{const s=await v("/api/maintenance/restart",{method:"POST",body:JSON.stringify({service:"box-console"})}),r=s.ok?"Konsolen-Dienst neu gestartet — einen Moment, dann lädt das Terminal.":`Neustart fehlgeschlagen: ${s.err||"Unbekannter Fehler"}`;l(r),m(s.ok?"erfolg":"fehler",r),w(u,f.agentStatus,f.services)}catch(s){const r=`Neustart fehlgeschlagen: ${(s==null?void 0:s.message)||s}`;l(r),m("fehler",r)}finally{i(!1)}}return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:x("h-2 w-2 rounded-full",n?"bg-emerald-500 animate-pulse":"bg-amber-500")}),n?"online":"offline"]}),a&&e.jsxs("a",{href:a,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(k,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),a?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[n===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(j,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der Terminal-Dienst (",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),") läuft gerade nicht."]}),e.jsxs("button",{onClick:b,disabled:o,className:"flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50",children:[e.jsx(y,{className:x("h-3.5 w-3.5",o&&"animate-spin")})," Dienst neu starten"]}),c&&e.jsx("p",{className:"max-w-sm text-[11px] text-muted-foreground",children:c})]}),e.jsx("iframe",{src:a,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(N,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{K as KonsoleView};
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as E,u as he,a as qe,r as v,j as e,C as Y,L as Fe,b as k,d as Re,q as V,R as Ie,B as re,e as Pe,E as ze,f as le,g as Be,h as Ae,i as Oe,k as Ge,l as ee,X as xe,H as be,T as se,s as Te,m as He,n as q,o as U,p as Qe,t as Ue,v as Ve,w as We,x as Ze,y as Je,z as Xe,A as Ye}from"./index-l6kyKCL9.js";import{J as et}from"./JobsBar-HflSaqor.js";import{C as tt}from"./code-xml-QQtsFWSM.js";import{C as ue,Z as ie,L as rt,a as st}from"./zap-CnZftvep.js";import{S as ge}from"./search-C1Zs6OOf.js";import{P as nt}from"./power-DcJoPDeQ.js";import{C as $e}from"./chevron-down-C5keVSfm.js";import{L as at}from"./layers-BFKjGswj.js";/**
|
||||
import{c as E,u as he,a as qe,r as v,j as e,C as Y,L as Fe,b as k,d as Re,q as V,R as Ie,B as re,e as Pe,E as ze,f as le,g as Be,h as Ae,i as Oe,k as Ge,l as ee,X as xe,H as be,T as se,s as Te,m as He,n as q,o as U,p as Qe,t as Ue,v as Ve,w as We,x as Ze,y as Je,z as Xe,A as Ye}from"./index-DMY9lUCb.js";import{J as et}from"./JobsBar-95gsUJZs.js";import{C as tt}from"./code-xml-Bf-y5tB9.js";import{C as ue,Z as ie,L as rt,a as st}from"./zap-2yha4rs4.js";import{S as ge}from"./search-D_8DnBvS.js";import{P as nt}from"./power-DsvD9qMX.js";import{C as $e}from"./chevron-down-1Vx9W9jM.js";import{L as at}from"./layers-CcL78b5Q.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
import{j as e}from"./index-l6kyKCL9.js";function n({icon:t,children:s}){return e.jsxs("p",{className:"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(t,{className:"h-3.5 w-3.5"})," ",s]})}export{n as S};
|
||||
import{j as e}from"./index-DMY9lUCb.js";function n({icon:t,children:s}){return e.jsxs("p",{className:"flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(t,{className:"h-3.5 w-3.5"})," ",s]})}export{n as S};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
var E=r=>{throw TypeError(r)};var O=(r,e,t)=>e.has(r)||E("Cannot "+t);var a=(r,e,t)=>(O(r,e,"read from private field"),t?t.call(r):e.get(r)),y=(r,e,t)=>e.has(r)?E("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(r):e.set(r,t),v=(r,e,t,n)=>(O(r,e,"write to private field"),n?n.call(r,t):e.set(r,t),t),j=(r,e,t)=>(O(r,e,"access private method"),t);import{a8 as Q,a9 as A,aa as M,ab as B,ac as P,u as J,r as f,ad as F,ae as I,c as T,af as U,j as s,J as D,T as H,b as K,L,Q as V,q as _,n as R}from"./index-l6kyKCL9.js";import{J as z}from"./JobsBar-HflSaqor.js";var c,g,o,h,m,w,C,q,G=(q=class extends Q{constructor(e,t){super();y(this,m);y(this,c);y(this,g);y(this,o);y(this,h);v(this,c,e),this.setOptions(t),this.bindMethods(),j(this,m,w).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){var n;const t=this.options;this.options=a(this,c).defaultMutationOptions(e),A(this.options,t)||a(this,c).getMutationCache().notify({type:"observerOptionsUpdated",mutation:a(this,o),observer:this}),t!=null&&t.mutationKey&&this.options.mutationKey&&M(t.mutationKey)!==M(this.options.mutationKey)?this.reset():((n=a(this,o))==null?void 0:n.state.status)==="pending"&&a(this,o).setOptions(this.options)}onUnsubscribe(){var e;this.hasListeners()||(e=a(this,o))==null||e.removeObserver(this)}onMutationUpdate(e){j(this,m,w).call(this),j(this,m,C).call(this,e)}getCurrentResult(){return a(this,g)}reset(){var e;(e=a(this,o))==null||e.removeObserver(this),v(this,o,void 0),j(this,m,w).call(this),j(this,m,C).call(this)}mutate(e,t){var n;return v(this,h,t),(n=a(this,o))==null||n.removeObserver(this),v(this,o,a(this,c).getMutationCache().build(a(this,c),this.options)),a(this,o).addObserver(this),a(this,o).execute(e)}},c=new WeakMap,g=new WeakMap,o=new WeakMap,h=new WeakMap,m=new WeakSet,w=function(){var t;const e=((t=a(this,o))==null?void 0:t.state)??B();v(this,g,{...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset})},C=function(e){P.batch(()=>{var t,n,l,x,d,p,k,i;if(a(this,h)&&this.hasListeners()){const u=a(this,g).variables,N=a(this,g).context,S={client:a(this,c),meta:this.options.meta,mutationKey:this.options.mutationKey};if((e==null?void 0:e.type)==="success"){try{(n=(t=a(this,h)).onSuccess)==null||n.call(t,e.data,u,N,S)}catch(b){Promise.reject(b)}try{(x=(l=a(this,h)).onSettled)==null||x.call(l,e.data,null,u,N,S)}catch(b){Promise.reject(b)}}else if((e==null?void 0:e.type)==="error"){try{(p=(d=a(this,h)).onError)==null||p.call(d,e.error,u,N,S)}catch(b){Promise.reject(b)}try{(i=(k=a(this,h)).onSettled)==null||i.call(k,void 0,e.error,u,N,S)}catch(b){Promise.reject(b)}}}this.listeners.forEach(u=>{u(a(this,g))})})},q);function W(r,e){const t=J(),[n]=f.useState(()=>new G(t,r));f.useEffect(()=>{n.setOptions(r)},[n,r]);const l=f.useSyncExternalStore(f.useCallback(d=>n.subscribe(P.batchCalls(d)),[n]),()=>n.getCurrentResult(),()=>n.getCurrentResult()),x=f.useCallback((d,p)=>{n.mutate(d,p).catch(F)},[n]);if(l.error&&I(n.options.throwOnError,[l.error]))throw l.error;return{...l,mutate:x,mutateAsync:l.mutate}}/**
|
||||
var E=r=>{throw TypeError(r)};var O=(r,e,t)=>e.has(r)||E("Cannot "+t);var a=(r,e,t)=>(O(r,e,"read from private field"),t?t.call(r):e.get(r)),y=(r,e,t)=>e.has(r)?E("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(r):e.set(r,t),v=(r,e,t,n)=>(O(r,e,"write to private field"),n?n.call(r,t):e.set(r,t),t),j=(r,e,t)=>(O(r,e,"access private method"),t);import{a6 as Q,a7 as A,a8 as M,a9 as B,aa as P,u as J,r as f,ab as F,ac as I,c as T,ad as U,j as s,J as D,T as H,b as K,L,Q as V,q as _,n as R}from"./index-DMY9lUCb.js";import{J as z}from"./JobsBar-95gsUJZs.js";var c,g,o,h,m,w,C,q,G=(q=class extends Q{constructor(e,t){super();y(this,m);y(this,c);y(this,g);y(this,o);y(this,h);v(this,c,e),this.setOptions(t),this.bindMethods(),j(this,m,w).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){var n;const t=this.options;this.options=a(this,c).defaultMutationOptions(e),A(this.options,t)||a(this,c).getMutationCache().notify({type:"observerOptionsUpdated",mutation:a(this,o),observer:this}),t!=null&&t.mutationKey&&this.options.mutationKey&&M(t.mutationKey)!==M(this.options.mutationKey)?this.reset():((n=a(this,o))==null?void 0:n.state.status)==="pending"&&a(this,o).setOptions(this.options)}onUnsubscribe(){var e;this.hasListeners()||(e=a(this,o))==null||e.removeObserver(this)}onMutationUpdate(e){j(this,m,w).call(this),j(this,m,C).call(this,e)}getCurrentResult(){return a(this,g)}reset(){var e;(e=a(this,o))==null||e.removeObserver(this),v(this,o,void 0),j(this,m,w).call(this),j(this,m,C).call(this)}mutate(e,t){var n;return v(this,h,t),(n=a(this,o))==null||n.removeObserver(this),v(this,o,a(this,c).getMutationCache().build(a(this,c),this.options)),a(this,o).addObserver(this),a(this,o).execute(e)}},c=new WeakMap,g=new WeakMap,o=new WeakMap,h=new WeakMap,m=new WeakSet,w=function(){var t;const e=((t=a(this,o))==null?void 0:t.state)??B();v(this,g,{...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset})},C=function(e){P.batch(()=>{var t,n,l,x,d,p,k,i;if(a(this,h)&&this.hasListeners()){const u=a(this,g).variables,N=a(this,g).context,S={client:a(this,c),meta:this.options.meta,mutationKey:this.options.mutationKey};if((e==null?void 0:e.type)==="success"){try{(n=(t=a(this,h)).onSuccess)==null||n.call(t,e.data,u,N,S)}catch(b){Promise.reject(b)}try{(x=(l=a(this,h)).onSettled)==null||x.call(l,e.data,null,u,N,S)}catch(b){Promise.reject(b)}}else if((e==null?void 0:e.type)==="error"){try{(p=(d=a(this,h)).onError)==null||p.call(d,e.error,u,N,S)}catch(b){Promise.reject(b)}try{(i=(k=a(this,h)).onSettled)==null||i.call(k,void 0,e.error,u,N,S)}catch(b){Promise.reject(b)}}}this.listeners.forEach(u=>{u(a(this,g))})})},q);function W(r,e){const t=J(),[n]=f.useState(()=>new G(t,r));f.useEffect(()=>{n.setOptions(r)},[n,r]);const l=f.useSyncExternalStore(f.useCallback(d=>n.subscribe(P.batchCalls(d)),[n]),()=>n.getCurrentResult(),()=>n.getCurrentResult()),x=f.useCallback((d,p)=>{n.mutate(d,p).catch(F)},[n]);if(l.error&&I(n.options.throwOnError,[l.error]))throw l.error;return{...l,mutate:x,mutateAsync:l.mutate}}/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as v,j as e,b as c,r as l,T as W,L as ae,E as ve,n as b,Q as le,Y as oe,X as de,J as Se,u as _e,k as Ce,x as ze,as as Fe,ag as Le,am as Ue,V as P,t as Ae,y as q,q as N}from"./index-l6kyKCL9.js";import{R as F}from"./refresh-cw-BV1qZMSR.js";import{F as Pe}from"./file-text-nO0jfgun.js";import{S as Be}from"./search-C1Zs6OOf.js";import{S as ce}from"./shield-CVm2T_f5.js";import{A as ne}from"./arrow-right-DSSXd-8y.js";import{C as Re}from"./clock-QfyF1ur_.js";import{E as $e}from"./external-link-ClFd8iN1.js";import{C as De,a as Ee}from"./cloud-download-YEWUSwEY.js";import{P as Oe}from"./power-DcJoPDeQ.js";/**
|
||||
import{c as v,j as e,b as c,r as l,T as W,L as ae,E as ve,n as b,Q as le,Y as oe,X as de,J as Se,u as _e,k as Ce,x as ze,aq as Fe,ae as Le,ak as Ue,V as P,t as Ae,y as q,q as N}from"./index-DMY9lUCb.js";import{R as F}from"./refresh-cw-hAcZthO8.js";import{F as Pe}from"./file-text-BSQuFPZM.js";import{S as Be}from"./search-D_8DnBvS.js";import{S as ce}from"./shield-BtE9Fxv6.js";import{A as ne}from"./arrow-right-CNrCtUoh.js";import{C as Re}from"./clock-Bth-s5Y3.js";import{E as $e}from"./external-link-DcJao8h4.js";import{C as De,a as Ee}from"./cloud-download-BGirmHxu.js";import{P as Oe}from"./power-DsvD9qMX.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
var to=Object.defineProperty;var eo=(e,n,r)=>n in e?to(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r;var Bn=(e,n,r)=>eo(e,typeof n!="symbol"?n+"":n,r);import{c as no,r as W,an as ro,ao as io,j as $,ap as oo,z as ao,A as so,aq as uo,n as lo,b as Ae,L as Ke,a1 as co,ar as fo}from"./index-l6kyKCL9.js";import{i as Ft,c as Xn,a as Yn,b as ho,o as po,m as Zn,d as Kn}from"./string-DoZi9Vij.js";import{R as go}from"./refresh-cw-BV1qZMSR.js";import{S as yo}from"./search-C1Zs6OOf.js";import{F as _o}from"./file-text-nO0jfgun.js";/**
|
||||
var to=Object.defineProperty;var eo=(e,n,r)=>n in e?to(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r;var Bn=(e,n,r)=>eo(e,typeof n!="symbol"?n+"":n,r);import{c as no,r as W,al as ro,am as io,j as $,an as oo,z as ao,A as so,ao as uo,n as lo,b as Ae,L as Ke,a1 as co,ap as fo}from"./index-DMY9lUCb.js";import{i as Ft,c as Xn,a as Yn,b as ho,o as po,m as Zn,d as Kn}from"./string-DoZi9Vij.js";import{R as go}from"./refresh-cw-hAcZthO8.js";import{S as yo}from"./search-D_8DnBvS.js";import{F as _o}from"./file-text-BSQuFPZM.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as r}from"./index-l6kyKCL9.js";/**
|
||||
import{c as r}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as o}from"./index-l6kyKCL9.js";/**
|
||||
import{c as o}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c}from"./index-l6kyKCL9.js";/**
|
||||
import{c}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
import{c as a}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as e}from"./index-l6kyKCL9.js";/**
|
||||
import{c as e}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
import{c as a}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as e}from"./index-l6kyKCL9.js";/**
|
||||
import{c as e}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const m=a("Hammer",[["path",{d:"m15 12-8.373 8.373a1 1 0 1 1-3-3L12 9",key:"eefl8a"}],["path",{d:"m18 15 4-4",key:"16gjal"}],["path",{d:"m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172V7l-2.26-2.26a6 6 0 0 0-4.202-1.756L9 2.96l.92.82A6.18 6.18 0 0 1 12 8.4V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5",key:"b7pghm"}]]);export{m as H};
|
||||
+1
@@ -0,0 +1 @@
|
||||
import{ay as r}from"./index-DMY9lUCb.js";var o=r();export{o as r};
|
||||
+264
File diff suppressed because one or more lines are too long
-1
@@ -1 +0,0 @@
|
||||
import{aA as r}from"./index-l6kyKCL9.js";var o=r();export{o as r};
|
||||
-269
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
import{c as a}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as o}from"./index-l6kyKCL9.js";/**
|
||||
import{c as o}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as e}from"./index-l6kyKCL9.js";/**
|
||||
import{c as e}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c}from"./index-l6kyKCL9.js";/**
|
||||
import{c}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
import{c as a}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import{c as a}from"./index-l6kyKCL9.js";/**
|
||||
import{c as a}from"./index-DMY9lUCb.js";/**
|
||||
* @license lucide-react v0.460.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-l6kyKCL9.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DMY9lUCb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-d4K0juid.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -25,7 +25,6 @@ const ConnectView = lazy(() => import("@/views/ConnectView").then((m) => ({ defa
|
||||
const AgentView = lazy(() => import("@/views/AgentView").then((m) => ({ default: m.AgentView })))
|
||||
const KonsoleView = lazy(() => import("@/views/KonsoleView").then((m) => ({ default: m.KonsoleView })))
|
||||
const GuideView = lazy(() => import("@/views/GuideView").then((m) => ({ default: m.GuideView })))
|
||||
const AuftragsbuchView = lazy(() => import("@/views/AuftragsbuchView").then((m) => ({ default: m.AuftragsbuchView })))
|
||||
const IdeenView = lazy(() => import("@/features/ideen/IdeenAnsicht").then((m) => ({ default: m.IdeenView })))
|
||||
const SkillsView = lazy(() => import("@/views/SkillsView").then((m) => ({ default: m.SkillsView })))
|
||||
const ChronikView = lazy(() => import("@/views/ChronikView").then((m) => ({ default: m.ChronikView })))
|
||||
@@ -99,7 +98,6 @@ function CockpitSeite() {
|
||||
}
|
||||
|
||||
const cockpitRoute = ansicht("/cockpit", () => <CockpitSeite />)
|
||||
const auftraegeRoute = ansicht("/auftraege", () => <AuftragsbuchView />)
|
||||
const skillsRoute = ansicht("/skills", () => <SkillsView />)
|
||||
/** Chronik: `?q=` filtert den Verlauf — Ziel der „Finden"-Einträge der Palette (P6). */
|
||||
export interface ChronikSuche {
|
||||
@@ -178,7 +176,6 @@ export const routeTree = rootRoute.addChildren([
|
||||
indexRoute,
|
||||
cockpitRoute,
|
||||
ideenRoute,
|
||||
auftraegeRoute,
|
||||
skillsRoute,
|
||||
chronikRoute,
|
||||
wissenRoute,
|
||||
|
||||
@@ -481,7 +481,7 @@ function IdeenSection() {
|
||||
<Wahl aktiv={modus === "box"}
|
||||
onWahl={() => setModus("box")}
|
||||
k={{ id: "box", icon: Hammer, titel: "Die Box, allein",
|
||||
text: "Für dein Heimlabor. Das Ergebnis kommt als Patch-Vorschlag ins Auftragsbuch." }} />
|
||||
text: "Für dein Heimlabor. Das Ergebnis kommt als Branch nach Gitea — mergen tust du." }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -490,46 +490,6 @@ export interface ModelsResp {
|
||||
running?: string[]
|
||||
}
|
||||
|
||||
// ── Auftragsbuch (Vorschlags-Inbox) ──────────────────────────────────────────
|
||||
export interface AuftragStatus {
|
||||
state: "laeuft" | "eingespielt" | "fehlgeschlagen" | "rollback" | "zurueckgerollt" | "kritisch" | "abgelehnt"
|
||||
detail: string
|
||||
ts: number
|
||||
}
|
||||
export interface AuftragItem {
|
||||
repo?: "mc2" | "lucy" // Quelle: Box-Stack (mc2) oder Lucy-Desktop (Annahme baut am PC)
|
||||
branch: string
|
||||
kind: string // wartung | orchestrator | doku | feature
|
||||
subject: string
|
||||
body: string
|
||||
author: string
|
||||
ts: number | null
|
||||
ahead: number
|
||||
behind: number
|
||||
shortstat: string
|
||||
files: { status: string; path: string }[]
|
||||
files_truncated: boolean
|
||||
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
|
||||
ci_status?: "success" | "failure" | "pending" | "skipped" | null
|
||||
// Empfehlungs-Stempel des nächtlichen Karten-Gutachters (Meinung, kein Gate).
|
||||
empfehlung: { empfehlung: "ANNEHMEN" | "ABLEHNEN" | "UNKLAR"; satz: string; richter: string } | null
|
||||
}
|
||||
export interface SkillKandidat {
|
||||
file: string
|
||||
title: string
|
||||
preview: string
|
||||
mtime: number
|
||||
}
|
||||
export interface AuftragsbuchResp {
|
||||
available: boolean
|
||||
items: AuftragItem[]
|
||||
skill_kandidaten: SkillKandidat[]
|
||||
open_count: number
|
||||
}
|
||||
|
||||
// ── Ideen-Queue (natives Hermes-Kanban) ──────────────────────────────────────
|
||||
export interface IdeenItem {
|
||||
id: string
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
api,
|
||||
type AgentStatus,
|
||||
type AktivitaetResp,
|
||||
type AuftragsbuchResp,
|
||||
type ChronikResp,
|
||||
type ConnectResp,
|
||||
type ConnectHealth,
|
||||
@@ -56,7 +55,6 @@ export const qk = {
|
||||
connectHealth: ["connect-health"] as const,
|
||||
memoryGraph: ["memory-graph"] as const,
|
||||
voiceTrace: ["voice-trace"] as const,
|
||||
auftragsbuch: ["auftragsbuch"] as const,
|
||||
ideen: ["ideen"] as const,
|
||||
chronik: ["chronik"] as const,
|
||||
wissen: ["wissen"] as const,
|
||||
@@ -71,7 +69,7 @@ export const qk = {
|
||||
// Intervalle mehr (mehrere Beobachter = kleinstes Intervall gewinnt).
|
||||
const TAKT = {
|
||||
graph: 3_000, // System + Token: seit P4 nur noch Rueckfall/Randdaten (siehe unten)
|
||||
schnell: 8_000, // „fühlt sich live an": Modelle, Voice-Trace, Auftragsbuch
|
||||
schnell: 8_000, // „fühlt sich live an": Modelle, Voice-Trace
|
||||
normal: 10_000, // Dienste, Routing, Agent-Status, Health
|
||||
gemuetlich: 30_000, // Chronik, Ideen
|
||||
traege: 60_000, // Updates-Check, Reminders, Zeitmaschine, HF-Abfragen
|
||||
@@ -96,15 +94,6 @@ const relax = (ms: number) => () => (sseVerbunden() ? ms * 5 : ms)
|
||||
// 3 s → 60 s ist eine Zwanzigstel-Last; abschalten hätte diese Felder einfrieren lassen.
|
||||
const langsamWennStrom = (ms: number) => () => (sseVerbunden() ? 60_000 : ms)
|
||||
|
||||
// Auftragsbuch: Vorschlags-Inbox (Branches + Skill-Kandidaten). Pollt, damit laufende
|
||||
// Annahme-Läufe (Status aus der JSON-Datei des Runners) live sichtbar werden.
|
||||
export const useAuftragsbuch = (refetchInterval: number = TAKT.schnell) =>
|
||||
useQuery({
|
||||
queryKey: qk.auftragsbuch,
|
||||
queryFn: () => api<AuftragsbuchResp>("/api/auftragsbuch"),
|
||||
refetchInterval: relax(refetchInterval),
|
||||
})
|
||||
|
||||
// Ideen-Queue (natives Hermes-Kanban): das Backend cached ~15 s, öfter pollen lohnt nicht.
|
||||
export const useIdeen = (refetchInterval: number = TAKT.gemuetlich) =>
|
||||
useQuery({
|
||||
|
||||
+1
-3
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Boxes,
|
||||
Inbox,
|
||||
History,
|
||||
Library,
|
||||
Plug,
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
export type ViewId = "dashboard" | "ideen" | "auftraege" | "skills" | "models" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
|
||||
export type ViewId = "dashboard" | "ideen" | "skills" | "models" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
|
||||
|
||||
export type NavGroup = "Operativ" | "Wissen" | "Werkzeuge" | "Wartung"
|
||||
|
||||
@@ -35,7 +34,6 @@ export const NAV: NavItem[] = [
|
||||
// Hinweis bewusst kurz: die App zeigt ihn oben in Großbuchstaben, direkt über der
|
||||
// Seitenüberschrift „Ideen" — ein langer Satz stand dort dreifach gedoppelt.
|
||||
{ id: "ideen", pfad: "/ideen", label: "Ideen", hint: "Schreib hin, was entstehen soll", icon: Lightbulb, group: "Operativ" },
|
||||
{ id: "auftraege", pfad: "/auftraege", label: "Auftragsbuch", hint: "Vorschläge der Box — annehmen oder ablehnen", icon: Inbox, group: "Operativ" },
|
||||
{ id: "skills", pfad: "/skills", label: "Skills & Jobs", hint: "Autonome Skills manuell auslösen", icon: Bot, group: "Operativ" },
|
||||
{ id: "chronik", pfad: "/chronik", label: "Chronik", hint: "Was die Box von allein getan hat + Zeitmaschine", icon: History, group: "Operativ" },
|
||||
{ id: "wissen", pfad: "/wissen", label: "Wissen", hint: "Lucys Wissens-Vault (Traum-Notizen)", icon: Library, group: "Wissen" },
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
import { useState } from "react"
|
||||
import {
|
||||
Inbox, Check, X, GitBranch, FileDiff, Loader2, AlertTriangle, Sparkles,
|
||||
ChevronDown, ChevronRight, Clock, Hammer, RefreshCw, Lightbulb, ArrowRight,
|
||||
} from "lucide-react"
|
||||
import { useNavigate } from "@tanstack/react-router"
|
||||
import { api, type AuftragItem, type SkillKandidat } from "@/lib/api"
|
||||
import { useAuftragsbuch, useIdeen, useQueryClient, qk } from "@/lib/queries"
|
||||
import { useDialog } from "@/lib/useDialog"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { fmtWhen } from "@/lib/format"
|
||||
import { SectionLabel } from "@/components/ui/SectionLabel"
|
||||
|
||||
// Auftragsbuch = die Vorschlags-Inbox der Box. Werkstatt/Orchestrator liefern propose-only-
|
||||
// Branches, der Traum liefert Skill-Kandidaten — hier fällt die Entscheidung per Klick.
|
||||
// Annehmen (Branch) = Merge→Push→Deploy→Health mit Auto-Rollback; alles detached auf der Box.
|
||||
|
||||
const KIND_LABEL: Record<string, string> = {
|
||||
wartung: "Werkstatt",
|
||||
orchestrator: "Orchestrator",
|
||||
doku: "Doku",
|
||||
feature: "Feature",
|
||||
}
|
||||
|
||||
const STATE_UI: Record<string, { label: string; cls: string; spin?: boolean }> = {
|
||||
laeuft: { label: "wird eingespielt …", cls: "text-sky-300 border-sky-500/40 bg-sky-500/10", spin: true },
|
||||
rollback: { label: "Rollback läuft …", cls: "text-amber-300 border-amber-500/40 bg-amber-500/10", spin: true },
|
||||
eingespielt: { label: "eingespielt ✓", cls: "text-emerald-300 border-emerald-500/40 bg-emerald-500/10" },
|
||||
fehlgeschlagen: { label: "fehlgeschlagen", cls: "text-red-300 border-red-500/40 bg-red-500/10" },
|
||||
zurueckgerollt: { label: "zurückgerollt", cls: "text-amber-300 border-amber-500/40 bg-amber-500/10" },
|
||||
kritisch: { label: "KRITISCH — Box prüfen", cls: "text-red-200 border-red-500/60 bg-red-500/20" },
|
||||
abgelehnt: { label: "abgelehnt", cls: "text-muted-foreground border-border/60 bg-background/30" },
|
||||
}
|
||||
|
||||
|
||||
export function AuftragsbuchView() {
|
||||
const navigate = useNavigate()
|
||||
const { data, isLoading, refetch, isFetching } = useAuftragsbuch()
|
||||
// Geteilter Cache mit der IdeenSection — hier nur fürs ehrliche "Nichts zu entscheiden"-
|
||||
// Banner (eine hängende Karte direkt darunter widerspräche dem grünen Häkchen).
|
||||
const { data: ideenData } = useIdeen()
|
||||
const qc = useQueryClient()
|
||||
const { showAlert, showConfirm, dialogElement } = useDialog()
|
||||
const [busy, setBusy] = useState<Record<string, boolean>>({})
|
||||
|
||||
const reload = () => qc.invalidateQueries({ queryKey: qk.auftragsbuch })
|
||||
|
||||
async function act(key: string, path: string, body: object, done?: string) {
|
||||
setBusy((b) => ({ ...b, [key]: true }))
|
||||
try {
|
||||
await api(path, { method: "POST", body: JSON.stringify(body) })
|
||||
if (done) showAlert("Erledigt", done)
|
||||
reload()
|
||||
} catch (e: any) {
|
||||
showAlert("Das hat nicht geklappt", String(e?.message || e))
|
||||
} finally {
|
||||
setBusy((b) => ({ ...b, [key]: false }))
|
||||
}
|
||||
}
|
||||
|
||||
const items = data?.items ?? []
|
||||
const kandidaten = data?.skill_kandidaten ?? []
|
||||
// Hängende Ideen leben seit 25.07. auf der eigenen Ideen-Seite. Das grüne „Nichts zu
|
||||
// entscheiden" darf trotzdem nicht behaupten, alles sei erledigt — es zeigt jetzt einen
|
||||
// Querverweis statt die Karte selbst (vorher stand sie direkt darunter).
|
||||
const blockedIdeen = ideenData?.available ? ideenData.items.filter((i) => i.status === "blocked").length : 0
|
||||
const empty = items.length === 0 && kandidaten.length === 0
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent">
|
||||
Auftragsbuch
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Was die Box gebaut oder sich ausgedacht hat — du entscheidest. Annehmen spielt es mit Fangnetz ein (Health-Check, automatischer Rollback).
|
||||
</p>
|
||||
</div>
|
||||
<button onClick={() => refetch()}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-border/60 bg-card/45 px-3 text-xs font-semibold text-muted-foreground hover:text-foreground hover:border-primary/40 transition-all cursor-pointer">
|
||||
<RefreshCw className={cn("h-3.5 w-3.5", isFetching && "animate-spin")} /> Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{data && !data.available && (
|
||||
<div className="rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300">
|
||||
Das Auftragsbuch lebt auf der Box (liest die Gitea-Branches dort). Im lokalen Dev-Modus ist hier nichts zu sehen.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Vorschläge werden geladen …
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data?.available && empty && (
|
||||
<div className="flex items-center gap-3 rounded-2xl border border-emerald-500/25 bg-emerald-500/5 p-5">
|
||||
<Check className="h-5 w-5 shrink-0 text-emerald-400" />
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-foreground">Nichts zu entscheiden</p>
|
||||
<p className="text-xs text-muted-foreground">Keine offenen Vorschläge. Werkstatt, Orchestrator und Traum melden sich hier, sobald es etwas gibt.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{blockedIdeen > 0 && (
|
||||
<button
|
||||
onClick={() => navigate({ to: "/ideen" })}
|
||||
className="flex w-full items-center gap-3 rounded-2xl border border-red-500/30 bg-red-500/5 p-4 text-left transition-all hover:border-red-500/50 cursor-pointer">
|
||||
<Lightbulb className="h-5 w-5 shrink-0 text-red-300" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold text-foreground">
|
||||
{blockedIdeen === 1 ? "Eine Idee hängt und braucht dich" : `${blockedIdeen} Ideen hängen und brauchen dich`}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">Hier ist nichts zu tun — aber drüben in den Ideen wartet eine Frage auf deine Antwort.</p>
|
||||
</div>
|
||||
<ArrowRight className="h-4 w-4 shrink-0 text-red-300/70" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{items.length > 0 && (
|
||||
<section className="space-y-3">
|
||||
<SectionLabel icon={GitBranch}>Patch-Vorschläge ({items.length})</SectionLabel>
|
||||
{items.map((it) => (
|
||||
<ProposalCard key={`${it.repo ?? "mc2"}:${it.branch}`} item={it} busy={busy} onAct={act} showConfirm={showConfirm} />
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{kandidaten.length > 0 && (
|
||||
<section className="space-y-3">
|
||||
<SectionLabel icon={Sparkles}>Skill-Kandidaten aus dem Traum ({kandidaten.length})</SectionLabel>
|
||||
{kandidaten.map((k) => (
|
||||
<KandidatCard key={k.file} k={k} busy={busy} onAct={act} showConfirm={showConfirm} />
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{dialogElement}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function StateBadge({ item }: { item: AuftragItem }) {
|
||||
const st = item.status
|
||||
if (!st) return null
|
||||
const ui = STATE_UI[st.state]
|
||||
if (!ui) return null
|
||||
return (
|
||||
<span className={cn("flex items-center gap-1.5 rounded-lg border px-2 py-0.5 text-[10px] font-bold", ui.cls)} title={st.detail}>
|
||||
{ui.spin && <Loader2 className="h-3 w-3 animate-spin" />}
|
||||
{ui.label}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function CIAmpelBadge({ item }: { item: AuftragItem }) {
|
||||
const ci = item.ci_status
|
||||
if (!ci) return null
|
||||
|
||||
if (ci === "success") {
|
||||
return <span className="flex items-center gap-1 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-bold text-emerald-400" title="CI-Ampel: Grün (Baut & läuft)"><Check className="h-3 w-3" /> CI Grün</span>
|
||||
}
|
||||
if (ci === "failure") {
|
||||
return <span className="flex items-center gap-1 rounded-lg border border-red-500/30 bg-red-500/10 px-1.5 py-0.5 text-[10px] font-bold text-red-400" title="CI-Ampel: Rot (Fehler)"><X className="h-3 w-3" /> CI Rot</span>
|
||||
}
|
||||
if (ci === "pending") {
|
||||
return <span className="flex items-center gap-1 rounded-lg border border-amber-500/30 bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-bold text-amber-400" title="CI-Ampel: Läuft"><Loader2 className="h-3 w-3 animate-spin" /> CI Läuft</span>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function ProposalCard({
|
||||
item, busy, onAct, showConfirm,
|
||||
}: {
|
||||
item: AuftragItem
|
||||
busy: Record<string, boolean>
|
||||
onAct: (key: string, path: string, body: object, done?: string) => void
|
||||
showConfirm: (title: string, msg: string, ok: () => void) => void
|
||||
}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [diff, setDiff] = useState<string | null>(null)
|
||||
const [diffLoading, setDiffLoading] = useState(false)
|
||||
// Ablehnen mit Grund: der Grund ist das Lern-Gedächtnis des Kreislaufs (Radar/Specifier
|
||||
// bekommen ihn als „nie wieder vorschlagen"-Material) — deshalb ein kleines Inline-Feld.
|
||||
const [rejecting, setRejecting] = useState(false)
|
||||
const [grund, setGrund] = useState("")
|
||||
const running = item.status?.state === "laeuft" || item.status?.state === "rollback"
|
||||
const decided = item.status?.state === "eingespielt"
|
||||
// Lucy-Karten (Desktop-App): Annehmen baut am PC (dist) statt auf der Box zu deployen.
|
||||
const repo = item.repo ?? "mc2"
|
||||
const isLucy = repo === "lucy"
|
||||
const k = `${repo}:${item.branch}`
|
||||
|
||||
async function loadDiff() {
|
||||
const next = !open
|
||||
setOpen(next)
|
||||
if (next && diff === null) {
|
||||
setDiffLoading(true)
|
||||
try {
|
||||
const r = await api<{ diff: string; truncated: boolean }>(`/api/auftragsbuch/diff?branch=${encodeURIComponent(item.branch)}&repo=${repo}`)
|
||||
setDiff(r.diff + (r.truncated ? "\n… (gekürzt)" : ""))
|
||||
} catch (e: any) {
|
||||
setDiff(`(Diff nicht ladbar: ${e?.message || e})`)
|
||||
} finally {
|
||||
setDiffLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mc-card p-4 space-y-3">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded-md border border-primary/30 bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-primary">
|
||||
{KIND_LABEL[item.kind] ?? item.kind}
|
||||
</span>
|
||||
{isLucy && (
|
||||
<span className="rounded-md border border-pink-500/40 bg-pink-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-pink-300"
|
||||
title="Vorschlag fürs Lucy-Repo — Annehmen baut die App am PC neu und startet sie neu, wenn sie läuft.">
|
||||
Lucy
|
||||
</span>
|
||||
)}
|
||||
<span className="font-mono text-[11px] text-muted-foreground truncate" title={item.branch}>{item.branch}</span>
|
||||
<span className="flex items-center gap-1 text-[10px] text-muted-foreground/70"><Clock className="h-3 w-3" />{fmtWhen(item.ts)}</span>
|
||||
<CIAmpelBadge item={item} />
|
||||
<StateBadge item={item} />
|
||||
</div>
|
||||
<p className="mt-1.5 text-sm font-semibold text-foreground">{item.subject || "(ohne Titel)"}</p>
|
||||
{item.body && <p className="mt-1 whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground line-clamp-6">{item.body}</p>}
|
||||
{item.empfehlung && (
|
||||
<p className={cn("mt-1.5 flex items-start gap-1.5 rounded-lg border px-2 py-1 text-[11px]",
|
||||
item.empfehlung.empfehlung === "ANNEHMEN" && "border-emerald-500/30 bg-emerald-500/5 text-emerald-300",
|
||||
item.empfehlung.empfehlung === "ABLEHNEN" && "border-red-500/30 bg-red-500/5 text-red-300",
|
||||
item.empfehlung.empfehlung === "UNKLAR" && "border-amber-500/30 bg-amber-500/5 text-amber-300")}
|
||||
title={`Nächtliches Kurz-Gutachten (${item.empfehlung.richter}) — eine Meinung, kein Muss. Die Entscheidung bleibt bei dir.`}>
|
||||
<Sparkles className="mt-0.5 h-3 w-3 shrink-0" />
|
||||
<span><b>Empfehlung: {item.empfehlung.empfehlung === "ANNEHMEN" ? "Annehmen" : item.empfehlung.empfehlung === "ABLEHNEN" ? "Ablehnen" : "Unklar"}.</b> {item.empfehlung.satz}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!running && !decided && (
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{!item.verwaist && (
|
||||
<button
|
||||
disabled={!!busy[k]}
|
||||
onClick={() =>
|
||||
showConfirm("Vorschlag annehmen?",
|
||||
isLucy
|
||||
? `'${item.branch}' wird im Lucy-Repo auf main gemergt, dann baut dein PC die App neu (dist) und startet Lucy neu, falls sie gerade läuft. Der PC muss dafür an sein. Schlägt Build oder Start fehl, dreht die Box den Merge automatisch zurück — die laufende Lucy bleibt die alte.`
|
||||
: `'${item.branch}' wird auf main gemergt und live eingespielt. Die Zentrale startet dabei kurz neu. Geht der Health-Check danach rot, rollt die Box automatisch zurück.${item.frontend_ohne_build ? "\n\nAchtung: Enthält Frontend-Quelltext ohne gebautes Bundle — die Oberfläche bleibt alt, bis am PC gebaut wird." : ""}`,
|
||||
() => onAct(k, "/api/auftragsbuch/annehmen", { branch: item.branch, repo }))}
|
||||
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
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
disabled={!!busy["ab:" + k]}
|
||||
onClick={() => setRejecting((v) => !v)}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-red-500/30 bg-red-500/5 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300/90 transition-all hover:bg-red-500/15 cursor-pointer disabled:opacity-50">
|
||||
{busy["ab:" + k] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />} Ablehnen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{rejecting && !running && !decided && (
|
||||
<div className="space-y-2 rounded-xl border border-red-500/25 bg-red-500/5 p-3">
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Der Branch '{item.branch}' wird auf Gitea gelöscht (die Arbeit bleibt in der Historie).
|
||||
Sag der Box kurz <b>warum</b> — dann schlägt sie dir so etwas nicht wieder vor.
|
||||
</p>
|
||||
<input
|
||||
value={grund}
|
||||
onChange={(e) => setGrund(e.target.value)}
|
||||
placeholder={"Grund (optional) — z. B. „gibt es schon“ oder „will ich nicht“"}
|
||||
maxLength={400}
|
||||
className="w-full rounded-lg border border-border/60 bg-background/60 px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-red-400/50 focus:outline-none"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
disabled={!!busy["ab:" + k]}
|
||||
onClick={() => {
|
||||
setRejecting(false)
|
||||
onAct("ab:" + k, "/api/auftragsbuch/ablehnen", { branch: item.branch, repo, grund: grund.trim() })
|
||||
}}
|
||||
className="flex h-8 items-center gap-1.5 rounded-lg border border-red-500/40 bg-red-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300 transition-all hover:bg-red-500/20 cursor-pointer disabled:opacity-50">
|
||||
<X className="h-3.5 w-3.5" /> Wirklich ablehnen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRejecting(false)}
|
||||
className="flex h-8 items-center rounded-lg border border-border/60 bg-background/40 px-3 text-[11px] font-semibold text-muted-foreground transition-all hover:text-foreground cursor-pointer">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.status && (running || !decided) && item.status.detail && (
|
||||
<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">
|
||||
<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.verwaist && (
|
||||
<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
|
||||
</span>
|
||||
)}
|
||||
{item.frontend_ohne_build && (
|
||||
<span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="Die Box kann kein Frontend bauen (kein Node) — nach dem Annehmen bleibt die Oberfläche alt, bis am PC gebaut wird.">
|
||||
<AlertTriangle className="h-3 w-3" /> Frontend ohne Build
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button onClick={loadDiff}
|
||||
className="flex items-center gap-1.5 text-[11px] font-semibold text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
|
||||
{open ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
|
||||
<FileDiff className="h-3.5 w-3.5" /> {item.files.length} Datei{item.files.length === 1 ? "" : "en"} — Änderungen ansehen
|
||||
</button>
|
||||
{open && (
|
||||
<div className="mt-2 space-y-2">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{item.files.map((f) => (
|
||||
<span key={f.path} className="rounded bg-background/40 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
|
||||
<b className={cn(f.status.startsWith("A") && "text-emerald-400", f.status.startsWith("D") && "text-red-400", f.status.startsWith("M") && "text-sky-400")}>{f.status}</b> {f.path}
|
||||
</span>
|
||||
))}
|
||||
{item.files_truncated && <span className="text-[10px] text-muted-foreground/60">…</span>}
|
||||
</div>
|
||||
<div className="max-h-96 overflow-auto rounded-xl border border-border/50 bg-background/50 p-3">
|
||||
{diffLoading
|
||||
? <span className="text-[11px] text-muted-foreground">Diff wird geladen …</span>
|
||||
: <pre className="text-[10px] leading-relaxed text-foreground/80 whitespace-pre">{
|
||||
(diff ?? "").split("\n").map((ln, i) => (
|
||||
<span key={i} className={cn("block", ln.startsWith("+") && !ln.startsWith("+++") && "text-emerald-300/90 bg-emerald-500/5", ln.startsWith("-") && !ln.startsWith("---") && "text-red-300/90 bg-red-500/5", (ln.startsWith("@@") || ln.startsWith("diff ")) && "text-sky-300/90")}>{ln || " "}</span>
|
||||
))
|
||||
}</pre>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function KandidatCard({
|
||||
k, busy, onAct, showConfirm,
|
||||
}: {
|
||||
k: SkillKandidat
|
||||
busy: Record<string, boolean>
|
||||
onAct: (key: string, path: string, body: object, done?: string) => void
|
||||
showConfirm: (title: string, msg: string, ok: () => void) => void
|
||||
}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<div className="rounded-2xl border border-violet-500/25 bg-violet-500/[0.04] p-4 shadow-lg shadow-black/15 backdrop-blur-md space-y-3">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded-md border border-violet-500/30 bg-violet-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-violet-300">Traum-Idee</span>
|
||||
<span className="font-mono text-[11px] text-muted-foreground truncate">{k.file}</span>
|
||||
<span className="flex items-center gap-1 text-[10px] text-muted-foreground/70"><Clock className="h-3 w-3" />{fmtWhen(k.mtime)}</span>
|
||||
</div>
|
||||
<p className="mt-1.5 text-sm font-semibold text-foreground">{k.title}</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<button
|
||||
disabled={!!busy["sk:" + k.file]}
|
||||
onClick={() =>
|
||||
showConfirm("Werkstatt beauftragen?",
|
||||
`Der Kandidat '${k.title}' geht als Auftrag an die Werkstatt (Orchestrator, propose-only). Das Ergebnis erscheint später als neuer Patch-Vorschlag hier im Auftragsbuch — nichts geht ungefragt live.`,
|
||||
() => onAct("sk:" + k.file, "/api/auftragsbuch/skill/annehmen", { file: k.file },
|
||||
"Die Werkstatt arbeitet. Das Ergebnis taucht als neuer Vorschlag im Auftragsbuch auf (das kann einige Minuten dauern)."))}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-violet-500/40 bg-violet-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-violet-300 transition-all hover:bg-violet-500/20 cursor-pointer disabled:opacity-50">
|
||||
{busy["sk:" + k.file] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Hammer className="h-3.5 w-3.5" />} Beauftragen
|
||||
</button>
|
||||
<button
|
||||
disabled={!!busy["skab:" + k.file]}
|
||||
onClick={() =>
|
||||
showConfirm("Kandidat verwerfen?",
|
||||
`'${k.title}' wandert ins Archiv des Wissens-Vaults (verworfen/). Der Traum schlägt ihn nicht erneut vor.`,
|
||||
() => onAct("skab:" + k.file, "/api/auftragsbuch/skill/ablehnen", { file: k.file }))}
|
||||
className="flex h-9 items-center gap-1.5 rounded-lg border border-red-500/30 bg-red-500/5 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300/90 transition-all hover:bg-red-500/15 cursor-pointer disabled:opacity-50">
|
||||
{busy["skab:" + k.file] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />} Verwerfen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => setOpen((o) => !o)}
|
||||
className="flex items-center gap-1.5 text-[11px] font-semibold text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
|
||||
{open ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
|
||||
<Inbox className="h-3.5 w-3.5" /> Vorschlag lesen
|
||||
</button>
|
||||
{open && (
|
||||
<pre className="max-h-72 overflow-auto rounded-xl border border-border/50 bg-background/50 p-3 text-[11px] leading-relaxed text-foreground/85 whitespace-pre-wrap">{k.preview}</pre>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -168,9 +168,9 @@ export function GuideView() {
|
||||
<strong className="text-foreground"> Hermes Desktop</strong> für richtige Projekte und
|
||||
<strong className="text-foreground"> Telegram</strong> für unterwegs. In der Box denken die Modelle, das
|
||||
Gedächtnis lernt automatisch mit — und nachts arbeitet sie allein: Sie träumt, inventarisiert sich selbst,
|
||||
entwickelt Ideen zu fertigen Vorschlägen und legt sie dir als Karten ins Auftragsbuch.
|
||||
<strong className="text-primary"> Nichts geht live ohne deinen Klick</strong> — und wenn nach einer Annahme
|
||||
etwas rot wird, baut die Box es selbst zurück.
|
||||
entwickelt Ideen zu Konzepten. Gebaut wird am PC in der IDE, jede Änderung geht als Branch nach Gitea.
|
||||
<strong className="text-primary"> Nichts geht live ohne deinen Merge</strong> — die Ampel-CI muss grün sein,
|
||||
danach spielt <code>deploy.sh</code> den Stand ein.
|
||||
</p>
|
||||
<SystemSchaubild />
|
||||
</section>
|
||||
@@ -379,8 +379,8 @@ description: Treibt Entwicklung mit strikter TDD-Praxis
|
||||
Reden tust du mit ihm über <strong>Hermes Desktop</strong> (am PC, remote via <Pill>/hermes-ui</Pill>),
|
||||
die <strong>Konsole</strong> (Box-Shell im Browser), <strong>Telegram</strong> — oder per Stimme über
|
||||
<strong> Lucy</strong>. Er hat MCP-Server für Gedächtnis, Stack-Steuerung, Web-Fetch und
|
||||
<strong> PC-Steuerung</strong> (Executor auf deinem Windows-PC). Nachts betreibt er Werkstatt &
|
||||
Radar-Crons — alles Gebaute landet als Karte im <strong>Auftragsbuch</strong>.
|
||||
<strong> PC-Steuerung</strong> (Executor auf deinem Windows-PC). Nachts betreibt er Radar-Crons
|
||||
und meldet Funde in Chronik, Briefkasten und Telegram.
|
||||
Status & Verdrahtung im <strong>Hermes</strong>-Tab.
|
||||
</Stack>
|
||||
</Card>
|
||||
@@ -445,7 +445,7 @@ description: Treibt Entwicklung mit strikter TDD-Praxis
|
||||
<ul className="list-disc pl-4 space-y-1">
|
||||
<li><strong>Backup:</strong> PBS-Backup-Zentrum (Sicherung auf den Proxmox-Server) + tägliche Tarball-Sicherung; Restore echt durchgespielt (<Pill>restore.sh</Pill>, Doku in <Pill>docs/BACKUP.md</Pill>).</li>
|
||||
<li><strong>Updates:</strong> git-basierter Update-Check; <strong>Update-Buttons</strong> mit anschließendem <strong>Gehirn-Check</strong> (rot, falls ein Update das Gedächtnis zerschießt).</li>
|
||||
<li><strong>Annahme-Sicherheit:</strong> jede Auftragsbuch-Annahme läuft mit Health-Check — wird's rot, baut die Box automatisch zurück (Auto-Revert).</li>
|
||||
<li><strong>Deploy-Sicherheit:</strong> <code>deploy.sh</code> sichert vorher und prüft danach die Gesundheit; die Zeitmaschine in der Chronik ist der Rückweg.</li>
|
||||
<li><strong>Dienste & Gefahrenzone:</strong> Restart/Logs pro Dienst, kritische Eingriffe getrennt.</li>
|
||||
</ul>
|
||||
<p className="text-[10px] italic">Erreichbar über das System-Icon → Tab „Wartung".</p>
|
||||
|
||||
@@ -2,13 +2,13 @@ import { useState } from "react"
|
||||
import {
|
||||
Boxes, Server, ShieldAlert, Plug,
|
||||
HeartPulse, AlertTriangle, Loader2, Wrench, Check, HardDrive, ChevronRight,
|
||||
Inbox, HelpCircle,
|
||||
Lightbulb, HelpCircle,
|
||||
} from "lucide-react"
|
||||
import { useNavigate } from "@tanstack/react-router"
|
||||
import { api } from "@/lib/api"
|
||||
import {
|
||||
useModels, useServices, useUpdates, useAgentStatus, useConnectHealth,
|
||||
useAuftragsbuch, useIdeen, useQueryClient, qk,
|
||||
useIdeen, useQueryClient, qk,
|
||||
} from "@/lib/queries"
|
||||
import { useLucyHealth, type Repair } from "@/lib/useLucyHealth"
|
||||
import { useDialog } from "@/lib/useDialog"
|
||||
@@ -37,7 +37,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
const { data: updates } = useUpdates()
|
||||
const { data: agent } = useAgentStatus()
|
||||
const { data: connect } = useConnectHealth()
|
||||
const { data: auftraege } = useAuftragsbuch()
|
||||
const { data: ideen } = useIdeen()
|
||||
const health = useLucyHealth()
|
||||
|
||||
@@ -75,9 +74,8 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
const pendingCount =
|
||||
(updates ? (updates.os > 0 ? 1 : 0) + (updates.engine > 0 ? 1 : 0) + (updates.swap > 0 ? 1 : 0) + (updates.models > 0 ? 1 : 0) : 0) +
|
||||
(updates?.components?.filter((c) => c.update === true).length ?? 0)
|
||||
const openAuftraege = auftraege?.available ? auftraege.open_count : 0
|
||||
// Hängende Karten warten auf DEINE Antwort — bisher sah das Cockpit sie gar nicht
|
||||
// („Nichts zu tun" trotz roter Karte im Auftragsbuch, Review 15.07.).
|
||||
// Hängende Ideen warten auf DEINE Antwort — das Cockpit muss sie zeigen (Review 15.07.).
|
||||
const openIdeen = ideen?.available ? ideen.items.filter((i) => i.status !== "done").length : 0
|
||||
const blockedIdeen = ideen?.available ? ideen.items.filter((i) => i.status === "blocked").length : 0
|
||||
|
||||
// Verbinden: die drei Leitungen (Modell-Gateway · Gedächtnis · Desktop-Gateway).
|
||||
@@ -136,13 +134,13 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
<ZoneLabel>Bereiche</ZoneLabel>
|
||||
<div className="grid gap-4 grid-cols-2 md:grid-cols-3">
|
||||
<CockpitTile
|
||||
icon={Inbox} title="Auftragsbuch" value={auftraege?.available ? openAuftraege : "—"}
|
||||
unit={openAuftraege === 1 ? "Vorschlag" : "Vorschläge"}
|
||||
tone={!auftraege ? "loading" : blockedIdeen > 0 || openAuftraege > 0 ? "warn" : "ok"}
|
||||
icon={Lightbulb} title="Ideen" value={ideen?.available ? openIdeen : "—"}
|
||||
unit="offen"
|
||||
tone={!ideen ? "loading" : blockedIdeen > 0 ? "warn" : "ok"}
|
||||
hint={blockedIdeen > 0
|
||||
? `${blockedIdeen} Karte${blockedIdeen === 1 ? "" : "n"} wartet auf deine Antwort`
|
||||
: openAuftraege > 0 ? `${openAuftraege} offene Patches` : "Keine ausstehenden Vorschläge"}
|
||||
onClick={() => onNavigate("auftraege")}
|
||||
? `${blockedIdeen} Idee${blockedIdeen === 1 ? "" : "n"} wartet auf deine Antwort`
|
||||
: openIdeen > 0 ? `${openIdeen} in Arbeit oder wartend` : "Nichts offen"}
|
||||
onClick={() => onNavigate("ideen")}
|
||||
/>
|
||||
<CockpitTile
|
||||
icon={Boxes} title="Modelle" value={running} unit="warm"
|
||||
@@ -180,7 +178,6 @@ export function CockpitView({ onNavigate }: { onNavigate: (v: string) => void })
|
||||
<NeedsYou
|
||||
problems={health.problems}
|
||||
pendingCount={pendingCount}
|
||||
openAuftraege={openAuftraege}
|
||||
blockedIdeen={blockedIdeen}
|
||||
busy={busy}
|
||||
onRepair={runRepair}
|
||||
@@ -258,13 +255,12 @@ function StatusLine({ verdict, memory, warns, problems }: {
|
||||
}
|
||||
|
||||
// „Braucht dich": kritische Probleme (mit 1-Klick-Reparatur) + bereitliegende Updates
|
||||
// + offene Vorschläge im Auftragsbuch.
|
||||
// + hängende Ideen.
|
||||
function NeedsYou({
|
||||
problems, pendingCount, openAuftraege, blockedIdeen, busy, onRepair, onNavigate,
|
||||
problems, pendingCount, blockedIdeen, busy, onRepair, onNavigate,
|
||||
}: {
|
||||
problems: ReturnType<typeof useLucyHealth>["problems"]
|
||||
pendingCount: number
|
||||
openAuftraege: number
|
||||
blockedIdeen: number
|
||||
busy: Record<string, boolean>
|
||||
onRepair: (id: string, r: Repair) => void
|
||||
@@ -272,9 +268,8 @@ function NeedsYou({
|
||||
}) {
|
||||
const openDrawer = useSchublade()
|
||||
const hasUpdates = pendingCount > 0
|
||||
const hasAuftraege = openAuftraege > 0
|
||||
const hasBlocked = blockedIdeen > 0
|
||||
const nothing = problems.length === 0 && !hasUpdates && !hasAuftraege && !hasBlocked
|
||||
const nothing = problems.length === 0 && !hasUpdates && !hasBlocked
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col justify-between">
|
||||
@@ -313,7 +308,7 @@ function NeedsYou({
|
||||
))}
|
||||
{hasBlocked && (
|
||||
<button
|
||||
onClick={() => onNavigate("auftraege")}
|
||||
onClick={() => onNavigate("ideen")}
|
||||
className="flex w-full items-center justify-between gap-3 rounded-2xl border border-red-500/30 bg-red-500/5 p-4 text-left transition-all hover:bg-red-500/10 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -328,23 +323,6 @@ function NeedsYou({
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-red-300/70" />
|
||||
</button>
|
||||
)}
|
||||
{hasAuftraege && (
|
||||
<button
|
||||
onClick={() => onNavigate("auftraege")}
|
||||
className="flex w-full items-center justify-between gap-3 rounded-2xl border border-primary/30 bg-primary/5 p-4 text-left transition-all hover:bg-primary/10 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-primary/30 bg-background/30 text-primary">
|
||||
<Inbox className="h-4.5 w-4.5" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-sm font-bold text-foreground">{openAuftraege} Vorschl{openAuftraege === 1 ? "ag" : "äge"} im Auftragsbuch</p>
|
||||
<p className="text-xs text-muted-foreground">Annehmen oder ablehnen.</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 shrink-0 text-primary/70" />
|
||||
</button>
|
||||
)}
|
||||
{hasUpdates && (
|
||||
<button
|
||||
onClick={() => openDrawer("wartung")}
|
||||
|
||||
@@ -94,7 +94,7 @@ function Landkarte() {
|
||||
</text>
|
||||
|
||||
<NodeBox x={444} y={54} w={396} h={62} color={T.teal} title="Mission Control (:9001)"
|
||||
sub="Cockpit · Auftragsbuch (Klick-Gate) · Chronik · Wissen" />
|
||||
sub="Cockpit · Ideen · Chronik · Wissen" />
|
||||
<NodeBox x={444} y={126} w={396} h={62} color={T.rose} title="Hermes-Agent"
|
||||
sub="denkt, nutzt Werkzeuge, betreibt die Werkstatt & Nacht-Crons" />
|
||||
<NodeBox x={444} y={198} w={396} h={78} color={T.sky} title="Modell-Stall (llama-swap)"
|
||||
@@ -125,14 +125,14 @@ function Kreislauf() {
|
||||
const stationen = [
|
||||
{ x: 14, color: T.amber, title: "Idee", sub: "von dir oder vom", sub2: "Nacht-Radar" },
|
||||
{ x: 158, color: T.violet, title: "Ideen-Queue", sub: "Kanban sammelt,", sub2: "Specifier plant" },
|
||||
{ x: 302, color: T.rose, title: "Werkstatt", sub: "Agent baut den", sub2: "Vorschlag (Branch)" },
|
||||
{ x: 446, color: T.sky, title: "Auftragsbuch", sub: "Karte + Stempel", sub2: "vom Gutachter" },
|
||||
{ x: 590, color: T.primary, title: "DEIN Klick", sub: "annehmen oder", sub2: "ablehnen (mit Grund)", strong: true },
|
||||
{ x: 734, color: T.emerald, title: "Live", sub: "Merge + Deploy,", sub2: "Rot = Auto-Rückbau" },
|
||||
{ x: 302, color: T.rose, title: "IDE am PC", sub: "Agent baut den", sub2: "Branch (Gitea)" },
|
||||
{ x: 446, color: T.sky, title: "Ampel", sub: "CI prüft Build", sub2: "und Tests" },
|
||||
{ x: 590, color: T.primary, title: "DEIN Merge", sub: "nur du bringst", sub2: "es auf main", strong: true },
|
||||
{ x: 734, color: T.emerald, title: "Live", sub: "deploy.sh,", sub2: "Zeitmaschine = Rückweg" },
|
||||
]
|
||||
return (
|
||||
<svg viewBox="0 0 880 246" role="img"
|
||||
aria-label="Kreislauf: Idee, Ideen-Queue, Werkstatt, Auftragsbuch, dein Klick, live — Chronik und Morgenlage berichten zurück."
|
||||
aria-label="Kreislauf: Idee, Ideen-Queue, IDE am PC, Ampel, dein Merge, live — Chronik und Morgenlage berichten zurück."
|
||||
style={{ width: "100%", height: "auto", fontFamily: "var(--font-sans)" }}>
|
||||
|
||||
{stationen.map((s) => (
|
||||
|
||||
Reference in New Issue
Block a user