Skip to content
This repository was archived by the owner on Jun 28, 2026. It is now read-only.

Commit d59f39d

Browse files
olasunkanmi.raymondolasunkanmi.raymond
authored andcommitted
healthStatsteam-graph-handlerhealthStats
1 parent bc2b323 commit d59f39d

6 files changed

Lines changed: 365 additions & 307 deletions

File tree

src/webview-providers/handlers/team-graph-handler.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,35 @@ export class TeamGraphHandler implements WebviewMessageHandler {
139139
}
140140
}
141141

142-
// Health stats
142+
// Health stats — send structured data alongside markdown
143143
const healthMarkdown = store.getTeamHealth();
144+
const totalCommitments = people.reduce(
145+
(s, p) => s + p.commitment_count,
146+
0,
147+
);
148+
const totalCompleted = people.reduce(
149+
(s, p) => s + p.completion_count,
150+
0,
151+
);
152+
const summaries = store.getRecentSummaries(10000);
144153

145154
await ctx.webview.webview.postMessage({
146155
command: "team-hydrate-result",
147156
members,
148157
edges,
149158
health: healthMarkdown,
159+
healthStats: {
160+
teamSize: members.length,
161+
standups: summaries.length,
162+
avgCompletion:
163+
totalCommitments > 0
164+
? Math.round((totalCompleted / totalCommitments) * 100)
165+
: 0,
166+
totalBlockers: summaries.reduce(
167+
(s, su) => s + su.blockerCount,
168+
0,
169+
),
170+
},
150171
});
151172
break;
152173
}

webviewUi/src/components/coworker/CoWorkerPanel.tsx

Lines changed: 1 addition & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import React, { useState, useEffect } from "react";
1+
import React from "react";
22
import styled from "styled-components";
33
import { vscode } from "../../utils/vscode";
4-
import { useStandupStore } from "../../stores/standup.store";
54
import { DoctorSection } from "./DoctorSection";
65

76
interface CoWorkerPanelProps {
@@ -214,54 +213,7 @@ const triggerCommand = (commandId: string) => {
214213

215214
/* ─── Meeting Intelligence Styled Components ─── */
216215

217-
const NotesTextArea = styled.textarea`
218-
width: 100%;
219-
min-height: 80px;
220-
max-height: 200px;
221-
resize: vertical;
222-
background: var(--vscode-input-background);
223-
color: var(--vscode-input-foreground);
224-
border: 1px solid var(--vscode-input-border, rgba(255, 255, 255, 0.12));
225-
border-radius: 4px;
226-
padding: 8px;
227-
font-size: 12px;
228-
font-family: var(--vscode-font-family);
229-
line-height: 1.4;
230-
box-sizing: border-box;
231-
232-
&::placeholder {
233-
color: var(--vscode-input-placeholderForeground);
234-
}
235-
236-
&:focus {
237-
outline: none;
238-
border-color: var(--vscode-focusBorder);
239-
}
240-
`;
241-
242-
const IngestButton = styled.button<{ $loading?: boolean }>`
243-
background: var(--vscode-button-background);
244-
color: var(--vscode-button-foreground);
245-
border: none;
246-
border-radius: 4px;
247-
padding: 6px 14px;
248-
cursor: ${(p) => (p.$loading ? "wait" : "pointer")};
249-
font-size: 12px;
250-
font-weight: 500;
251-
width: 100%;
252-
margin-top: 8px;
253-
opacity: ${(p) => (p.$loading ? 0.7 : 1)};
254-
transition: all 0.15s ease;
255216

256-
&:hover:not(:disabled) {
257-
background: var(--vscode-button-hoverBackground);
258-
}
259-
260-
&:disabled {
261-
opacity: 0.5;
262-
cursor: not-allowed;
263-
}
264-
`;
265217

266218
export const QuickActions = styled.div`
267219
display: flex;
@@ -270,76 +222,7 @@ export const QuickActions = styled.div`
270222
flex-wrap: wrap;
271223
`;
272224

273-
const QuickActionButton = styled.button`
274-
background: rgba(255, 255, 255, 0.08);
275-
border: 1px solid rgba(255, 255, 255, 0.12);
276-
border-radius: 4px;
277-
padding: 4px 10px;
278-
cursor: pointer;
279-
color: var(--vscode-foreground);
280-
font-size: 11px;
281-
white-space: nowrap;
282-
transition: all 0.15s ease;
283-
display: flex;
284-
align-items: center;
285-
gap: 4px;
286-
287-
&:hover {
288-
background: rgba(255, 255, 255, 0.14);
289-
border-color: rgba(255, 255, 255, 0.2);
290-
}
291-
292-
&:active {
293-
transform: scale(0.97);
294-
}
295-
`;
296-
297-
const RecentList = styled.div`
298-
margin-top: 10px;
299-
display: flex;
300-
flex-direction: column;
301-
gap: 4px;
302-
`;
303225

304-
const RecentItem = styled.div`
305-
display: flex;
306-
justify-content: space-between;
307-
align-items: center;
308-
padding: 6px 8px;
309-
border-radius: 4px;
310-
background: var(--vscode-list-hoverBackground, rgba(128, 128, 128, 0.08));
311-
font-size: 11px;
312-
`;
313-
314-
const RecentDate = styled.span`
315-
color: var(--vscode-foreground);
316-
font-weight: 500;
317-
`;
318-
319-
const RecentMeta = styled.span`
320-
color: var(--vscode-descriptionForeground);
321-
`;
322-
323-
const DeleteButton = styled.button`
324-
background: none;
325-
border: none;
326-
color: var(--vscode-descriptionForeground);
327-
cursor: pointer;
328-
padding: 2px 4px;
329-
font-size: 12px;
330-
border-radius: 3px;
331-
opacity: 0;
332-
transition: opacity 0.15s ease, color 0.15s ease;
333-
334-
${RecentItem}:hover & {
335-
opacity: 1;
336-
}
337-
338-
&:hover {
339-
color: var(--vscode-editorError-foreground, #f14c4c);
340-
background: rgba(241, 76, 76, 0.1);
341-
}
342-
`;
343226

344227
export const ErrorText = styled.div`
345228
color: var(--vscode-editorError-foreground, #f14c4c);
@@ -403,20 +286,6 @@ export const CoWorkerPanel: React.FC<CoWorkerPanelProps> = ({
403286
onGitWatchdogChange,
404287
onEndOfDaySummaryChange,
405288
}) => {
406-
const [notesInput, setNotesInput] = useState("");
407-
const { isIngesting, lastError, recentStandups, ingestNotes, requestMyTasks, requestBlockers, requestHistory, deleteStandup, deletingKey, hydrate } =
408-
useStandupStore();
409-
410-
// Rehydrate recent standups from backend when panel opens
411-
useEffect(() => {
412-
if (isOpen && recentStandups.length === 0) hydrate();
413-
}, [isOpen, recentStandups.length, hydrate]);
414-
const handleIngest = () => {
415-
if (!notesInput.trim() || isIngesting) return;
416-
ingestNotes(notesInput.trim());
417-
setNotesInput("");
418-
};
419-
420289
const toggleMap: Record<string, { checked: boolean; onChange: (v: boolean) => void }> = {
421290
"Daily Standup": { checked: dailyStandupEnabled, onChange: onDailyStandupChange },
422291
"Code Health Check": { checked: codeHealthEnabled, onChange: onCodeHealthChange },
@@ -469,68 +338,6 @@ export const CoWorkerPanel: React.FC<CoWorkerPanelProps> = ({
469338

470339
{/* ── Security Pulse (Doctor) Section ── */}
471340
<DoctorSection isOpen={isOpen} />
472-
473-
{/* ── Meeting Intelligence Section ── */}
474-
<Section>
475-
<SectionTitle>Meeting Intelligence</SectionTitle>
476-
<TaskDescription style={{ marginBottom: 8 }}>
477-
Paste meeting or standup notes to extract action items, blockers,
478-
and decisions automatically.
479-
</TaskDescription>
480-
<NotesTextArea
481-
placeholder="Paste your standup / meeting notes here..."
482-
value={notesInput}
483-
onChange={(e) => setNotesInput(e.target.value)}
484-
onKeyDown={(e) => {
485-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
486-
handleIngest();
487-
}
488-
}}
489-
/>
490-
<IngestButton
491-
$loading={isIngesting}
492-
disabled={isIngesting || !notesInput.trim()}
493-
onClick={handleIngest}
494-
>
495-
{isIngesting ? "⏳ Parsing..." : "📋 Parse Meeting Notes"}
496-
</IngestButton>
497-
{lastError && <ErrorText>{lastError}</ErrorText>}
498-
499-
<QuickActions>
500-
<QuickActionButton onClick={() => requestMyTasks()}>
501-
✅ My Tasks
502-
</QuickActionButton>
503-
<QuickActionButton onClick={() => requestBlockers()}>
504-
🔴 Blockers
505-
</QuickActionButton>
506-
<QuickActionButton onClick={() => requestHistory({ dateRange: "this week" })}>
507-
📅 This Week
508-
</QuickActionButton>
509-
</QuickActions>
510-
511-
{recentStandups.length > 0 && (
512-
<RecentList>
513-
{recentStandups.map((s) => (
514-
<RecentItem key={`${s.date}-${s.teamName}`}>
515-
<div>
516-
<RecentDate>{s.date}{s.teamName}</RecentDate>
517-
<RecentMeta>
518-
{s.commitmentCount} tasks · {s.blockerCount} blockers
519-
</RecentMeta>
520-
</div>
521-
<DeleteButton
522-
onClick={() => deleteStandup(s.date, s.teamName)}
523-
disabled={deletingKey === `${s.date}-${s.teamName}`}
524-
title={`Delete standup for ${s.date}`}
525-
aria-label={`Delete standup for ${s.date}`}
526-
>
527-
{deletingKey === `${s.date}-${s.teamName}` ? "⏳" : "🗑"}
528-
</DeleteButton>
529-
</RecentItem>
530-
))}
531-
</RecentList>
532-
)}
533-
</Section>
534341
</Content>
535342
</PanelContainer>
536343
</PanelOverlay>

0 commit comments

Comments
 (0)