Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions frontend/src/components/EventCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMitzoStore } from '@mitzo/client/hooks';
import type { CalendarEvent } from '../hooks/useCalendarData';
import { buildMeetingPrepPrompt, buildMeetingContext } from '../lib/calendar-utils';

function formatTime(isoStr: string): string {
if (!isoStr.includes('T')) return '';
Expand All @@ -9,6 +12,18 @@

export function EventCard({ event }: { event: CalendarEvent }) {
const [expanded, setExpanded] = useState(false);
const navigate = useNavigate();
const setPendingSession = useMitzoStore((s) => s.setPendingSession);

Check failure on line 16 in frontend/src/components/EventCard.tsx

View workflow job for this annotation

GitHub Actions / ci

frontend/src/pages/__tests__/CalendarView.test.tsx > CalendarView > navigates date forward on next click

Error: useMitzoStore must be used within a <MitzoStoreProvider> ❯ EventCard frontend/src/components/EventCard.tsx:16:29 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41 ❯ renderRootSync node_modules/react-dom/cjs/react-dom-client.development.js:17450:11

Check failure on line 16 in frontend/src/components/EventCard.tsx

View workflow job for this annotation

GitHub Actions / ci

frontend/src/pages/__tests__/CalendarView.test.tsx > CalendarView > renders sprint bar

Error: useMitzoStore must be used within a <MitzoStoreProvider> ❯ EventCard frontend/src/components/EventCard.tsx:16:29 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41 ❯ renderRootSync node_modules/react-dom/cjs/react-dom-client.development.js:17450:11

Check failure on line 16 in frontend/src/components/EventCard.tsx

View workflow job for this annotation

GitHub Actions / ci

frontend/src/pages/__tests__/CalendarView.test.tsx > CalendarView > renders meeting events

Error: useMitzoStore must be used within a <MitzoStoreProvider> ❯ EventCard frontend/src/components/EventCard.tsx:16:29 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41 ❯ renderRootSync node_modules/react-dom/cjs/react-dom-client.development.js:17450:11

Check failure on line 16 in frontend/src/components/EventCard.tsx

View workflow job for this annotation

GitHub Actions / ci

frontend/src/pages/__tests__/CalendarView.test.tsx > CalendarView > renders calendar header with navigation

Error: useMitzoStore must be used within a <MitzoStoreProvider> ❯ EventCard frontend/src/components/EventCard.tsx:16:29 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41 ❯ renderRootSync node_modules/react-dom/cjs/react-dom-client.development.js:17450:11

Check failure on line 16 in frontend/src/components/EventCard.tsx

View workflow job for this annotation

GitHub Actions / ci

Unhandled error

Error: useMitzoStore must be used within a <MitzoStoreProvider> ❯ EventCard frontend/src/components/EventCard.tsx:16:29 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41 ❯ renderRootSync node_modules/react-dom/cjs/react-dom-client.development.js:17450:11 This error originated in "frontend/src/pages/__tests__/CalendarView.test.tsx" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.

function handlePrepClick(e: React.MouseEvent) {
e.stopPropagation();
setPendingSession({
prompt: buildMeetingPrepPrompt(event),
context: buildMeetingContext(event),
agentName: 'mitzo-calendar',
});
navigate('/chat');
}

if (event.type === 'milestone') {
return (
Expand Down Expand Up @@ -70,17 +85,22 @@
)}
</div>
)}
{event.hangoutLink && (
<a
className="cal-detail-link"
href={event.hangoutLink}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
Join video call
</a>
)}
<div className="cal-event-actions">
<button className="cal-action-prep" onClick={handlePrepClick}>
Prep for this meeting
</button>
{event.hangoutLink && (
<a
className="cal-detail-link"
href={event.hangoutLink}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
Join video call
</a>
)}
</div>
</div>
)}
</div>
Expand Down
74 changes: 74 additions & 0 deletions frontend/src/lib/__tests__/calendar-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { describe, it, expect } from 'vitest';
import { buildMeetingPrepPrompt, buildMeetingContext } from '../calendar-utils';
import type { CalendarEvent } from '../../hooks/useCalendarData';

function makeEvent(overrides: Partial<CalendarEvent> = {}): CalendarEvent {
return {
id: 'evt-1',
type: 'meeting',
title: 'Standup',
start: '2026-05-22T10:00:00Z',
end: '2026-05-22T10:30:00Z',
...overrides,
};
}

describe('buildMeetingPrepPrompt', () => {
it('includes title and formatted time range', () => {
const result = buildMeetingPrepPrompt(makeEvent());
expect(result).toContain('Prepare for "Standup"');
expect(result).toContain(' at ');
});

it('omits time for all-day events (no T in start)', () => {
const result = buildMeetingPrepPrompt(makeEvent({ start: '2026-05-22', end: '2026-05-23' }));
expect(result).toBe('Prepare for "Standup"');
});

it('omits time when start is empty', () => {
const result = buildMeetingPrepPrompt(makeEvent({ start: '' }));
expect(result).toBe('Prepare for "Standup"');
});
});

describe('buildMeetingContext', () => {
it('includes meeting title', () => {
const result = buildMeetingContext(makeEvent());
expect(result).toContain('**Meeting:** Standup');
});

it('includes location when present', () => {
const result = buildMeetingContext(makeEvent({ location: 'Room 42' }));
expect(result).toContain('**Where:** Room 42');
});

it('omits location when absent', () => {
const result = buildMeetingContext(makeEvent());
expect(result).not.toContain('**Where:**');
});

it('formats attendees from email addresses', () => {
const result = buildMeetingContext(
makeEvent({ attendees: ['alice@example.com', 'bob@example.com'] }),
);
expect(result).toContain('**Attendees:** alice, bob');
});

it('truncates attendees beyond 10 with count', () => {
const attendees = Array.from({ length: 12 }, (_, i) => `user${i}@example.com`);
const result = buildMeetingContext(makeEvent({ attendees }));
expect(result).toContain('(+2 more)');
expect(result).not.toContain('user10');
});

it('includes video link when present', () => {
const result = buildMeetingContext(makeEvent({ hangoutLink: 'https://meet.google.com/abc' }));
expect(result).toContain('**Video:** [Join call](https://meet.google.com/abc)');
});

it('includes context prompts at the end', () => {
const result = buildMeetingContext(makeEvent());
expect(result).toContain('Review recent Jira activity');
expect(result).toContain('key topics and decisions');
});
});
68 changes: 68 additions & 0 deletions frontend/src/lib/calendar-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { CalendarEvent } from '../hooks/useCalendarData';

export function buildMeetingPrepPrompt(event: CalendarEvent): string {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 style: Multi-line JSDoc comments on buildMeetingPrepPrompt and buildMeetingContext restate what the function name already says. CLAUDE.md says 'default to writing no comments' and 'don't explain WHAT the code does'. Remove the doc blocks. [fixable]

const time = formatEventTime(event);
const when = time ? ` at ${time}` : '';

return `Prepare for "${event.title}"${when}`;
}

export function buildMeetingContext(event: CalendarEvent): string {
const lines: string[] = [];

lines.push(`**Meeting:** ${event.title}`);

if (event.start) {
const startDate = new Date(event.start);
const dateStr = startDate.toLocaleDateString([], {
weekday: 'short',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
lines.push(`**When:** ${dateStr}`);
}

if (event.location) {
lines.push(`**Where:** ${event.location}`);
}

if (event.attendees && event.attendees.length > 0) {
const displayAttendees = event.attendees
.slice(0, 10)
.map((email) => {
const name = email.split('@')[0];
return name;
})
.join(', ');

const suffix = event.attendees.length > 10 ? ` (+${event.attendees.length - 10} more)` : '';
lines.push(`**Attendees:** ${displayAttendees}${suffix}`);
}

if (event.hangoutLink) {
lines.push(`**Video:** [Join call](${event.hangoutLink})`);
}

lines.push('');
lines.push('**Context for this meeting:**');
lines.push('- Review recent Jira activity for attendees');
lines.push('- Check relevant docs and recent conversations');
lines.push('- Identify key topics and decisions needed');

return lines.join('\n');
}

function formatEventTime(event: CalendarEvent): string {
if (!event.start || !event.start.includes('T')) return '';

const start = new Date(event.start);
const end = event.end ? new Date(event.end) : null;

const startTime = start.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
if (!end) return startTime;

const endTime = end.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
return `${startTime}–${endTime}`;
}
1 change: 1 addition & 0 deletions frontend/src/pages/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export function ChatView() {
model: modelState,
mode,
...(pendingSession.telosTaskId ? { telosTaskId: pendingSession.telosTaskId } : {}),
...(pendingSession.agentName ? { agentName: pendingSession.agentName } : {}),
});
clearPendingSession();
forceScrollToBottom();
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/pages/TodoView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
defaultCollapsed: boolean;
}

export function groupIntoSections(items: TodoItem[]): TodoSection[] {

Check warning on line 20 in frontend/src/pages/TodoView.tsx

View workflow job for this annotation

GitHub Actions / ci

Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components
const focus: TodoItem[] = [];
const active: TodoItem[] = [];
const seen: TodoItem[] = [];
Expand Down Expand Up @@ -190,6 +190,8 @@
setPendingSession({
prompt: buildPrompt(item),
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 regressions: Adding telosTaskId: item.id is a behavioral change unrelated to the agentName feature — previously TodoView did not set telosTaskId on the pending session. If this was already handled elsewhere (e.g. by ChatView or the store), this could cause duplicate assignment. Confirm this is intentional and not already set by another path.

context: buildTodoContext(item),
telosTaskId: item.id,
agentName: 'mitzo-telos',
});
navigate('/chat');
}
Expand Down
29 changes: 28 additions & 1 deletion frontend/src/styles/calendar.css
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,39 @@
color: var(--text-dim);
}

.cal-event-actions {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-top: var(--space-2);
}

.cal-action-prep {
background: var(--accent);
color: #fff;
border: none;
padding: var(--space-2) var(--space-3);
border-radius: 6px;
font-size: var(--text-sm);
font-weight: 500;
cursor: pointer;
transition: opacity 0.15s;
}

.cal-action-prep:hover {
opacity: 0.9;
}

.cal-action-prep:active {
transform: scale(0.98);
}

.cal-detail-link {
display: inline-block;
margin-top: var(--space-1h);
color: var(--accent);
font-size: var(--text-xs);
text-decoration: none;
padding: var(--space-1) 0;
}

.cal-detail-link:hover {
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ export interface SendMessageOptions {
extraTools?: string;
isolation?: boolean;
telosTaskId?: string;
agentName?: string;
}

export interface PendingSession {
prompt: string;
context: string;
telosTaskId?: string;
agentName?: string;
}

export interface MitzoStoreState {
Expand Down Expand Up @@ -317,6 +319,7 @@ export function createMitzoStore(options: MitzoStoreOptions): StoreApi<MitzoStor
if (opts?.extraTools) msg.extraTools = opts.extraTools;
if (opts?.isolation !== undefined) msg.isolation = opts.isolation;
if (opts?.telosTaskId !== undefined) msg.telosTaskId = opts.telosTaskId;
if (opts?.agentName !== undefined) msg.agentName = opts.agentName;
return msg;
};

Expand Down
4 changes: 4 additions & 0 deletions packages/protocol/src/ws-schemas-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export const V2SendMessage = z.object({
images: z.array(ImageSchema).optional(),
contextBlocks: z.array(z.string()).optional(),
telosTaskId: z.string().optional(),
agentName: z
.string()
.regex(/^[a-zA-Z0-9_-]+$/)
.optional(),
});

export const V2InterruptMessage = z.object({
Expand Down
Loading
Loading