Merge main into recovery/corruption-incident-and-ui2-work

Fix merge conflicts intelligently:
- package.json: Use main's test script pattern (tests/guards/*.test.mjs && tests/**/*.test.ts)
- src/app/api/events/route.ts: Merge polling logic with telemetry event emission
- src/hooks/use-beads-subscription.ts: Merge event type handling (issues/telemetry/activity)

All changes preserve the new telemetry-based architecture while accepting
main's improved test coverage patterns.
This commit is contained in:
openhands 2026-02-16 06:50:09 +00:00
commit e74606da37
17 changed files with 454 additions and 49 deletions

View file

@ -1,5 +1,6 @@
'use client';
import { useMemo } from 'react';
import { motion } from 'framer-motion';
import type { KanbanFilterOptions, KanbanStats } from '../../lib/kanban';
@ -12,6 +13,7 @@ interface KanbanControlsProps {
filters: KanbanFilterOptions;
stats: KanbanStats;
epics: BeadIssue[];
issues: BeadIssue[];
onFiltersChange: (filters: KanbanFilterOptions) => void;
onNextActionable: () => void;
nextActionableFeedback?: string | null;
@ -21,6 +23,7 @@ export function KanbanControls({
filters,
stats,
epics,
issues,
onFiltersChange,
onNextActionable,
nextActionableFeedback = null,
@ -29,12 +32,24 @@ export function KanbanControls({
'ui-field rounded-xl px-3 py-2.5 text-sm outline-none transition';
// Build bead counts map for EpicChipStrip
const beadCounts = new Map<string, number>();
for (const epic of epics) {
// Count non-epic issues that belong to this epic
const count = epic.dependencies?.filter(d => d.type === 'parent' && d.target === epic.id).length ?? 0;
beadCounts.set(epic.id, count);
}
// Count non-epic issues that have this epic as their parent
const beadCounts = useMemo(() => {
const counts = new Map<string, number>();
for (const epic of epics) {
let count = 0;
for (const issue of issues) {
if (issue.issue_type === 'epic') continue;
const parentDep = issue.dependencies.find(d => d.type === 'parent');
const inferredParent = issue.id.includes('.') ? issue.id.split('.')[0] : null;
const parentEpicId = parentDep?.target ?? inferredParent;
if (parentEpicId === epic.id) {
count++;
}
}
counts.set(epic.id, count);
}
return counts;
}, [epics, issues]);
return (
<section className="grid gap-3">

View file

@ -230,6 +230,7 @@ export function KanbanPage({
filters={filters}
stats={stats}
epics={localIssues.filter((issue) => issue.issue_type === 'epic')}
issues={localIssues}
onFiltersChange={setFilters}
onNextActionable={handleNextActionable}
nextActionableFeedback={nextActionableFeedback}