repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
The Living OS cockpit
stars
latest
clone command
git clone gitlawb://did:key:z6Mku78K...XywC/living-os-cockp...git clone gitlawb://did:key:z6Mku78K.../living-os-cockp...59751530feat: surface worker supervisor health in live work5h ago| #1 | 'use client'; |
| #2 | |
| #3 | import { useState } from 'react'; |
| #4 | import useSWR from 'swr'; |
| #5 | import { sameOriginFetch, swrFetcher } from '@/lib/client-api'; |
| #6 | import { AlertTriangle, Check, Pencil, X } from 'lucide-react'; |
| #7 | |
| #8 | const fetcher = swrFetcher; |
| #9 | |
| #10 | function urgencyClass(urgency: string) { |
| #11 | if (urgency === 'high') return 'border-red-400/35 text-red-300'; |
| #12 | if (urgency === 'medium') return 'border-amber-400/35 text-amber-300'; |
| #13 | return 'border-blue-400/30 text-blue-300'; |
| #14 | } |
| #15 | |
| #16 | function plainInline(value: string) { |
| #17 | return value |
| #18 | .replace(/\*\*(.*?)\*\*/g, '$1') |
| #19 | .replace(/__(.*?)__/g, '$1') |
| #20 | .replace(/`([^`]+)`/g, '$1') |
| #21 | .trim(); |
| #22 | } |
| #23 | |
| #24 | function MarkdownPreview({ text }: { text: string }) { |
| #25 | const blocks: any[] = []; |
| #26 | let list: string[] = []; |
| #27 | |
| #28 | const flushList = (key: string) => { |
| #29 | if (!list.length) return; |
| #30 | blocks.push( |
| #31 | <ul key={key} className="ml-4 list-disc space-y-1 text-[color:var(--text-2)]"> |
| #32 | {list.map((item, index) => <li key={`${key}-${index}`}>{plainInline(item)}</li>)} |
| #33 | </ul>, |
| #34 | ); |
| #35 | list = []; |
| #36 | }; |
| #37 | |
| #38 | text.split(/\r?\n/).forEach((line, index) => { |
| #39 | const trimmed = line.trim(); |
| #40 | if (!trimmed) { |
| #41 | flushList(`list-${index}`); |
| #42 | return; |
| #43 | } |
| #44 | if (/^[-*]\s+/.test(trimmed)) { |
| #45 | list.push(trimmed.replace(/^[-*]\s+/, '')); |
| #46 | return; |
| #47 | } |
| #48 | |
| #49 | flushList(`list-${index}`); |
| #50 | if (/^---+$/.test(trimmed)) { |
| #51 | blocks.push(<div key={index} className="my-3 border-t border-[color:var(--border)]" />); |
| #52 | } else if (trimmed.startsWith('### ')) { |
| #53 | blocks.push(<h4 key={index} className="pt-2 text-sm font-semibold text-[color:var(--text-1)]">{plainInline(trimmed.slice(4))}</h4>); |
| #54 | } else if (trimmed.startsWith('## ')) { |
| #55 | blocks.push(<h3 key={index} className="pt-3 text-base font-semibold text-[color:var(--gold)]">{plainInline(trimmed.slice(3))}</h3>); |
| #56 | } else if (trimmed.startsWith('# ')) { |
| #57 | blocks.push(<h2 key={index} className="text-lg font-semibold text-[color:var(--text-1)]">{plainInline(trimmed.slice(2))}</h2>); |
| #58 | } else { |
| #59 | blocks.push(<p key={index} className="leading-6 text-[color:var(--text-2)]">{plainInline(trimmed)}</p>); |
| #60 | } |
| #61 | }); |
| #62 | flushList('list-final'); |
| #63 | |
| #64 | return <div className="space-y-2">{blocks}</div>; |
| #65 | } |
| #66 | |
| #67 | function DraftViewer({ item }: { item: any }) { |
| #68 | const draft = item.draft_view; |
| #69 | if (!draft) { |
| #70 | return ( |
| #71 | <pre className="mt-2 max-h-48 overflow-auto rounded border border-[color:var(--border)] bg-black/30 p-2 text-[10px] text-[color:var(--text-2)]"> |
| #72 | {JSON.stringify(item.context ?? {}, null, 2)} |
| #73 | </pre> |
| #74 | ); |
| #75 | } |
| #76 | |
| #77 | const solution = draft.solution_action ?? {}; |
| #78 | return ( |
| #79 | <div className="mt-3 space-y-3 border-t border-[color:var(--border)] pt-3"> |
| #80 | <div className="grid gap-2 text-xs md:grid-cols-3"> |
| #81 | <div> |
| #82 | <div className="uppercase tracking-wide text-[10px] text-[color:var(--text-3)]">Remedy category</div> |
| #83 | <div className="text-[color:var(--text-1)]">{draft.remedy_category || draft.category || 'Unassigned'}</div> |
| #84 | </div> |
| #85 | <div> |
| #86 | <div className="uppercase tracking-wide text-[10px] text-[color:var(--text-3)]">Article slug</div> |
| #87 | <div className="break-words text-[color:var(--text-1)]">{draft.slug || 'draft'}</div> |
| #88 | </div> |
| #89 | <div> |
| #90 | <div className="uppercase tracking-wide text-[10px] text-[color:var(--text-3)]">Source</div> |
| #91 | <div className={draft.source_exists ? 'text-emerald-300' : 'text-red-300'}> |
| #92 | {draft.source_exists ? 'Full draft loaded' : draft.source_error || 'Draft unavailable'} |
| #93 | </div> |
| #94 | </div> |
| #95 | </div> |
| #96 | |
| #97 | <div className="border-l border-[color:var(--gold)]/50 pl-3 text-xs"> |
| #98 | <div className="font-semibold text-[color:var(--text-1)]">{solution.label || 'Linked solution action'}</div> |
| #99 | <div className="mt-1 leading-5 text-[color:var(--text-2)]">{solution.summary || 'No solution action summary was attached.'}</div> |
| #100 | {solution.href && <div className="mt-1 text-[color:var(--gold)]">{solution.href}</div>} |
| #101 | </div> |
| #102 | |
| #103 | <div className="max-h-[540px] overflow-auto pr-2 text-sm"> |
| #104 | <MarkdownPreview text={draft.article_body || 'No article body was found for this draft.'} /> |
| #105 | </div> |
| #106 | </div> |
| #107 | ); |
| #108 | } |
| #109 | |
| #110 | function ApprovalBriefGrid({ item, compact = false }: { item: any; compact?: boolean }) { |
| #111 | const view = item.plain_view ?? {}; |
| #112 | const risk = view.warning |
| #113 | ? 'High' |
| #114 | : String(view.blast_radius || item.urgency || 'medium').split(/[.:]/)[0].replace(/_/g, ' '); |
| #115 | const impact = view.target || item.draft_view?.solution_action?.summary || view.what || 'This affects the next operator-reviewed action.'; |
| #116 | const recommendation = view.warning |
| #117 | ? 'Do not approve until the malformed context is understood.' |
| #118 | : view.if_approve |
| #119 | ? view.if_approve |
| #120 | : 'Review the detail, then approve only if the action matches King’s intent.'; |
| #121 | return ( |
| #122 | <div className={`approval-brief-grid ${compact ? 'compact' : ''}`}> |
| #123 | <div> |
| #124 | <span>Risk</span> |
| #125 | <strong>{risk}</strong> |
| #126 | </div> |
| #127 | <div> |
| #128 | <span>Impact</span> |
| #129 | <strong>{impact}</strong> |
| #130 | </div> |
| #131 | <div> |
| #132 | <span>Recommendation</span> |
| #133 | <strong>{recommendation}</strong> |
| #134 | </div> |
| #135 | </div> |
| #136 | ); |
| #137 | } |
| #138 | |
| #139 | function ApprovalDetailView({ item }: { item: any }) { |
| #140 | const view = item.plain_view ?? {}; |
| #141 | const rows = [ |
| #142 | ['WHAT', view.what], |
| #143 | ['WHY', view.why], |
| #144 | ['TARGET', view.target], |
| #145 | ['BLAST RADIUS', view.blast_radius], |
| #146 | ['IF I APPROVE', view.if_approve], |
| #147 | ['IF I DENY', view.if_deny], |
| #148 | ].filter(([, value]) => Boolean(value)); |
| #149 | |
| #150 | return ( |
| #151 | <div className="mt-3 space-y-2 rounded border border-[color:var(--border)] bg-black/20 p-3 text-xs"> |
| #152 | {view.warning && ( |
| #153 | <div className="flex items-start gap-2 rounded border border-red-400/30 bg-red-950/20 p-2 text-red-200"> |
| #154 | <AlertTriangle size={14} className="mt-0.5 shrink-0" /> |
| #155 | <span>{view.warning}</span> |
| #156 | </div> |
| #157 | )} |
| #158 | {rows.map(([label, value]) => ( |
| #159 | <div key={label} className="grid gap-1 md:grid-cols-[120px_1fr]"> |
| #160 | <div className="font-semibold tracking-wide text-[color:var(--gold)]">{label}</div> |
| #161 | <div className="leading-5 text-[color:var(--text-1)]">{value}</div> |
| #162 | </div> |
| #163 | ))} |
| #164 | </div> |
| #165 | ); |
| #166 | } |
| #167 | |
| #168 | export default function ApprovalQueuePanel({ compact = false }: { compact?: boolean }) { |
| #169 | const { data, mutate } = useSWR('/api/approval-queue?status=pending', fetcher, { refreshInterval: 6000 }); |
| #170 | const [editing, setEditing] = useState<string | null>(null); |
| #171 | const [expanded, setExpanded] = useState<Record<string, boolean>>({}); |
| #172 | const [feedback, setFeedback] = useState(''); |
| #173 | const [draftBody, setDraftBody] = useState(''); |
| #174 | const decisions = data?.decisions ?? []; |
| #175 | |
| #176 | const startEdit = (item: any) => { |
| #177 | setEditing(item.id); |
| #178 | setFeedback(item.feedback || ''); |
| #179 | setDraftBody(item.draft_view?.article_body || ''); |
| #180 | }; |
| #181 | |
| #182 | const act = async (item: any, action: 'approve' | 'deny' | 'edit') => { |
| #183 | const payload: Record<string, any> = { id: item.id, action, feedback }; |
| #184 | if (editing === item.id && item.draft_view) { |
| #185 | payload.draft_body = draftBody; |
| #186 | } |
| #187 | await sameOriginFetch('/api/approval-queue', { |
| #188 | method: 'POST', |
| #189 | headers: { 'Content-Type': 'application/json' }, |
| #190 | body: JSON.stringify(payload), |
| #191 | }); |
| #192 | setEditing(null); |
| #193 | setFeedback(''); |
| #194 | setDraftBody(''); |
| #195 | await mutate(); |
| #196 | }; |
| #197 | |
| #198 | return ( |
| #199 | <div id="approval-queue" className={compact ? 'max-h-[520px] space-y-3 overflow-y-auto pr-1' : 'space-y-3'}> |
| #200 | {!data && <div className="text-sm text-[color:var(--text-3)]">Loading approvals...</div>} |
| #201 | {data && decisions.length === 0 && ( |
| #202 | <div className="text-sm text-[color:var(--text-3)]">No pending approvals.</div> |
| #203 | )} |
| #204 | {decisions.map((item: any, index: number) => ( |
| #205 | <div key={item.id} className={`approval-card-shell stagger-row rounded border bg-[color:var(--bg-1)] p-3 ${compact ? 'compact' : ''} ${urgencyClass(item.urgency)}`} style={{ '--stagger-index': index } as any}> |
| #206 | <div className="approval-card-face"> |
| #207 | <div className="min-w-0 flex-1"> |
| #208 | <strong className="approval-card-title text-[color:var(--text-1)]">{item.title}</strong> |
| #209 | <ApprovalBriefGrid item={item} compact={compact} /> |
| #210 | </div> |
| #211 | <div className="approval-card-actions flex shrink-0 gap-1.5"> |
| #212 | <button className="btn-gold rounded px-2 py-1 text-xs inline-flex items-center gap-1" onClick={() => act(item, 'approve')}> |
| #213 | <Check size={12} /> Approve |
| #214 | </button> |
| #215 | <button className="btn-ghost rounded px-2 py-1 text-xs inline-flex items-center gap-1" onClick={() => act(item, 'deny')}> |
| #216 | <X size={12} /> Deny |
| #217 | </button> |
| #218 | <button className="btn-ghost rounded px-2 py-1 text-xs inline-flex items-center gap-1" onClick={() => startEdit(item)}> |
| #219 | <Pencil size={12} /> Edit |
| #220 | </button> |
| #221 | </div> |
| #222 | </div> |
| #223 | |
| #224 | <button |
| #225 | type="button" |
| #226 | className="mt-2 text-left text-xs text-[color:var(--text-3)] hover:text-[color:var(--text-2)]" |
| #227 | onClick={() => setExpanded(current => ({ ...current, [item.id]: !current[item.id] }))} |
| #228 | > |
| #229 | {expanded[item.id] |
| #230 | ? 'Hide detail' |
| #231 | : item.draft_view |
| #232 | ? 'Open detail, draft article, and raw context' |
| #233 | : 'Open detail and raw technical context'} |
| #234 | </button> |
| #235 | {expanded[item.id] && ( |
| #236 | <div className="mt-2 text-xs"> |
| #237 | <ApprovalDetailView item={item} /> |
| #238 | <DraftViewer item={item} /> |
| #239 | </div> |
| #240 | )} |
| #241 | |
| #242 | {editing === item.id && ( |
| #243 | <div className="mt-3 space-y-2"> |
| #244 | {item.draft_view && ( |
| #245 | <textarea |
| #246 | value={draftBody} |
| #247 | onChange={event => setDraftBody(event.target.value)} |
| #248 | className="h-80 w-full rounded border border-[color:var(--border)] bg-black/30 p-3 text-xs leading-5 text-[color:var(--text-1)] outline-none" |
| #249 | placeholder="Edit the full article draft" |
| #250 | /> |
| #251 | )} |
| #252 | <textarea |
| #253 | value={feedback} |
| #254 | onChange={event => setFeedback(event.target.value)} |
| #255 | className="h-20 w-full rounded border border-[color:var(--border)] bg-black/30 p-2 text-xs text-[color:var(--text-1)] outline-none" |
| #256 | placeholder="Optional edit note or approval condition" |
| #257 | /> |
| #258 | <div className="flex flex-wrap gap-2"> |
| #259 | <button className="btn-gold rounded px-3 py-1.5 text-xs" onClick={() => act(item, 'edit')}>Save edit</button> |
| #260 | <button className="btn-gold rounded px-3 py-1.5 text-xs inline-flex items-center gap-1" onClick={() => act(item, 'approve')}> |
| #261 | <Check size={12} /> Approve edited draft |
| #262 | </button> |
| #263 | </div> |
| #264 | </div> |
| #265 | )} |
| #266 | </div> |
| #267 | ))} |
| #268 | </div> |
| #269 | ); |
| #270 | } |
| #271 |