refactor: migrate agent graph to feature slice
This commit is contained in:
parent
3fddb4eafd
commit
c8f9d9bbdd
30 changed files with 203 additions and 65 deletions
|
|
@ -262,6 +262,12 @@ A smaller feature may skip `core/` and `preload/` when it is:
|
|||
- not adding a new use case
|
||||
- not adding a new transport boundary
|
||||
|
||||
If the feature still owns meaningful pure semantics or projection rules, keep
|
||||
`core/` and skip only the process layers you do not need.
|
||||
|
||||
Example:
|
||||
- `src/features/agent-graph` keeps `core/domain` and `renderer`, but does not add fake `main/` or `preload/` folders because the transport boundary lives elsewhere.
|
||||
|
||||
## Definition Of Done For A Reference Feature
|
||||
|
||||
A feature is reference-quality when:
|
||||
|
|
@ -295,3 +301,15 @@ Use it as the example for:
|
|||
- renderer dumb UI + hook orchestration
|
||||
- browser-friendly transport direction
|
||||
- feature-level lint guard rails
|
||||
|
||||
## Agent Graph As The Thin-Slice Reference
|
||||
|
||||
`src/features/agent-graph` is the thin-slice example for a renderer integration
|
||||
feature built on top of a reusable package.
|
||||
|
||||
Use it as the example for:
|
||||
|
||||
- keeping pure graph semantics in `core/domain`
|
||||
- exposing a renderer-only public entrypoint
|
||||
- integrating `packages/agent-graph` without inventing fake process layers
|
||||
- migrating legacy `src/renderer/features/*` code into the canonical feature root
|
||||
|
|
|
|||
|
|
@ -227,6 +227,77 @@ export default defineConfig([
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'feature-agent-graph-public-entrypoints',
|
||||
files: ['src/**/*.{ts,tsx}'],
|
||||
ignores: ['src/features/agent-graph/**/*'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: [
|
||||
'@features/agent-graph/core/**',
|
||||
'@features/agent-graph/renderer/**',
|
||||
],
|
||||
message:
|
||||
'Import agent-graph only through its public entrypoint: @features/agent-graph/renderer.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'feature-agent-graph-core-domain-guards',
|
||||
files: ['src/features/agent-graph/core/domain/**/*.ts'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: [
|
||||
'@features/agent-graph/renderer/**',
|
||||
'@main/**',
|
||||
'@renderer/**',
|
||||
'@preload/**',
|
||||
'electron',
|
||||
'fastify',
|
||||
'child_process',
|
||||
'node:child_process',
|
||||
],
|
||||
message:
|
||||
'agent-graph core/domain must stay pure and cannot depend on renderer, main, preload, or platform code.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'feature-agent-graph-renderer-boundaries',
|
||||
files: ['src/features/agent-graph/renderer/**/*.{ts,tsx}'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: [
|
||||
'@main/**',
|
||||
'@preload/**',
|
||||
'electron',
|
||||
],
|
||||
message:
|
||||
'agent-graph renderer may depend on shared, renderer, package, and feature-local modules, but not on main/preload or Electron APIs directly.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Module boundaries - Enforce Electron three-process architecture
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Before creating or refactoring a feature, read:
|
|||
|
||||
Reference implementation:
|
||||
- `src/features/recent-projects`
|
||||
- `src/features/agent-graph`
|
||||
|
||||
Use `src/features/<feature-name>/` by default when the work introduces:
|
||||
- a new use case or business policy
|
||||
|
|
@ -17,3 +18,7 @@ Use `src/features/<feature-name>/` by default when the work introduces:
|
|||
|
||||
Do not duplicate architecture rules in feature folders.
|
||||
Keep the standard centralized in [../../docs/FEATURE_ARCHITECTURE_STANDARD.md](../../docs/FEATURE_ARCHITECTURE_STANDARD.md).
|
||||
|
||||
Rule of thumb:
|
||||
- `recent-projects` is the full slice example with process-aware outer layers
|
||||
- `agent-graph` is the thin slice example built around `core/` plus `renderer/`
|
||||
|
|
|
|||
20
src/features/agent-graph/README.md
Normal file
20
src/features/agent-graph/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Agent Graph Feature
|
||||
|
||||
This feature is a thin renderer slice over the reusable graph engine in `packages/agent-graph`.
|
||||
|
||||
Read first:
|
||||
- [Feature Architecture Standard](../../docs/FEATURE_ARCHITECTURE_STANDARD.md)
|
||||
- [Feature root guidance](../CLAUDE.md)
|
||||
|
||||
Public entrypoint:
|
||||
- `@features/agent-graph/renderer`
|
||||
|
||||
Responsibilities:
|
||||
- `packages/agent-graph` owns reusable graph rendering and low-level graph mechanics
|
||||
- `src/features/agent-graph/core/domain` owns project-specific graph semantics and pure projection helpers
|
||||
- `src/features/agent-graph/renderer` owns the renderer integration layer, hooks, adapters, and UI
|
||||
|
||||
Use this feature as the thin-slice example when a feature:
|
||||
- has no dedicated `main` or `preload` transport boundary
|
||||
- integrates an existing reusable package into the app shell
|
||||
- still needs its own feature boundary and public entrypoint
|
||||
|
|
@ -160,7 +160,7 @@ export function buildInlineActivityEntries({
|
|||
for (const [ownerNodeId, entries] of entriesByOwnerNodeId) {
|
||||
entriesByOwnerNodeId.set(
|
||||
ownerNodeId,
|
||||
entries.sort((a, b) => b.graphItem.timestamp.localeCompare(a.graphItem.timestamp))
|
||||
entries.toSorted((a, b) => b.graphItem.timestamp.localeCompare(a.graphItem.timestamp))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
/**
|
||||
* TeamGraphAdapter — transforms Zustand TeamData → GraphDataPort.
|
||||
*
|
||||
* This is the ONLY file in this feature that imports from @renderer/store.
|
||||
* If the project data model changes, ONLY this class needs updating.
|
||||
* This adapter owns the graph projection from team runtime state into the
|
||||
* reusable package port model. Renderer hooks may still read store state, but
|
||||
* projection rules stay here so the mapping logic has one main reason to change.
|
||||
*
|
||||
* Class-based with ES #private fields and DI-ready constructor.
|
||||
*/
|
||||
|
|
@ -26,16 +27,15 @@ import { isLeadMember } from '@shared/utils/leadDetection';
|
|||
import {
|
||||
buildInlineActivityEntries,
|
||||
getGraphLeadMemberName,
|
||||
} from '../utils/buildInlineActivityEntries';
|
||||
import { collapseOverflowStacksWithMeta } from '../utils/collapseOverflowStacks';
|
||||
} from '../../core/domain/buildInlineActivityEntries';
|
||||
import { collapseOverflowStacksWithMeta } from '../../core/domain/collapseOverflowStacks';
|
||||
import {
|
||||
isTaskBlocked,
|
||||
isTaskInReviewCycle,
|
||||
resolveTaskReviewer,
|
||||
} from '../utils/taskGraphSemantics';
|
||||
} from '../../core/domain/taskGraphSemantics';
|
||||
|
||||
import type {
|
||||
GraphActivityItem,
|
||||
GraphDataPort,
|
||||
GraphEdge,
|
||||
GraphNode,
|
||||
|
|
@ -571,7 +571,10 @@ export class TeamGraphAdapter {
|
|||
|
||||
for (const relatedId of task.related ?? []) {
|
||||
if (!visibleTaskIds.has(relatedId)) continue;
|
||||
const key = [task.id, relatedId].sort().join(':');
|
||||
const key =
|
||||
task.id.localeCompare(relatedId) <= 0
|
||||
? `${task.id}:${relatedId}`
|
||||
: `${relatedId}:${task.id}`;
|
||||
if (this.#seenRelated.has(key)) continue;
|
||||
this.#seenRelated.add(key);
|
||||
edges.push({
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '@renderer/store/slices/teamSlice';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import { TeamGraphAdapter } from './TeamGraphAdapter';
|
||||
import { TeamGraphAdapter } from '../adapters/TeamGraphAdapter';
|
||||
|
||||
import type { GraphDataPort } from '@claude-teams/agent-graph';
|
||||
|
||||
12
src/features/agent-graph/renderer/index.ts
Normal file
12
src/features/agent-graph/renderer/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* agent-graph renderer feature - public API.
|
||||
*
|
||||
* Consumers outside the feature should import from here instead of reaching
|
||||
* into ui/, hooks/, or core/ directly.
|
||||
*/
|
||||
|
||||
export { buildInlineActivityEntries } from '../core/domain/buildInlineActivityEntries';
|
||||
export { TeamGraphAdapter } from './adapters/TeamGraphAdapter';
|
||||
export type { TeamGraphOverlayProps } from './ui/TeamGraphOverlay';
|
||||
export { TeamGraphOverlay } from './ui/TeamGraphOverlay';
|
||||
export { TeamGraphTab } from './ui/TeamGraphTab';
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
buildInlineActivityEntries,
|
||||
getGraphLeadMemberName,
|
||||
type InlineActivityEntry,
|
||||
} from '../utils/buildInlineActivityEntries';
|
||||
} from '../../core/domain/buildInlineActivityEntries';
|
||||
|
||||
import type { GraphNode } from '@claude-teams/agent-graph';
|
||||
import type { TimelineItem } from '@renderer/components/team/activity/LeadThoughtsGroup';
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* GraphNodePopover — renders popover for graph nodes using project UI components.
|
||||
* Lives in features/ (not in package) so it CAN import from @renderer/.
|
||||
* Reuses agentAvatarUrl, status helpers, and UI primitives from the project.
|
||||
* This stays in the renderer slice instead of the reusable package because it
|
||||
* composes project-specific UI, selectors, and presentation helpers.
|
||||
*/
|
||||
|
||||
import { Badge } from '@renderer/components/ui/badge';
|
||||
|
|
@ -16,7 +16,7 @@ import { buildTeamProvisioningPresentation } from '@renderer/utils/teamProvision
|
|||
import { ExternalLink, Loader2, MessageSquare, Plus, User } from 'lucide-react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import { isTaskInReviewCycle, resolveTaskReviewer } from '../utils/taskGraphSemantics';
|
||||
import { isTaskInReviewCycle, resolveTaskReviewer } from '../../core/domain/taskGraphSemantics';
|
||||
|
||||
import { GraphTaskCard } from './GraphTaskCard';
|
||||
|
||||
|
|
@ -40,11 +40,22 @@ function formatToolPreview(preview: string | undefined): string | undefined {
|
|||
// If it looks like raw JSON object, try to extract a readable field
|
||||
if (preview.startsWith('{') || preview.startsWith('[')) {
|
||||
try {
|
||||
const obj = JSON.parse(preview.length > 200 ? preview.slice(0, 200) : preview);
|
||||
// Common readable fields
|
||||
return (
|
||||
obj.subject ?? obj.name ?? obj.label ?? obj.file_path ?? obj.path ?? obj.query ?? undefined
|
||||
);
|
||||
const parsed: unknown = JSON.parse(preview.length > 200 ? preview.slice(0, 200) : preview);
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
const previewRecord = parsed as Record<string, unknown>;
|
||||
const candidates = [
|
||||
previewRecord.subject,
|
||||
previewRecord.name,
|
||||
previewRecord.label,
|
||||
previewRecord.file_path,
|
||||
previewRecord.path,
|
||||
previewRecord.query,
|
||||
];
|
||||
const firstText = candidates.find((value) => typeof value === 'string');
|
||||
if (typeof firstText === 'string') {
|
||||
return firstText;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Truncated JSON — extract first quoted value
|
||||
const match = /"(?:subject|name|label|path|query)":\s*"([^"]{1,60})"/.exec(preview);
|
||||
|
|
@ -421,7 +432,7 @@ const MemberPopoverContent = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* TODO: Context usage disabled — LeadContextUsage.percent unreliable (jumps) */}
|
||||
{/* Context usage stays hidden for now because LeadContextUsage.percent is unreliable. */}
|
||||
|
||||
{/* Current task indicator — reuses same pattern as MemberCard */}
|
||||
{node.currentTaskId && node.currentTaskSubject && (
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* GraphTaskCard — wraps the REAL KanbanTaskCard with graph-specific glow/pulse effects.
|
||||
* Lives in features/ so it CAN import from @renderer/.
|
||||
* This is a renderer integration component, so it is allowed to compose
|
||||
* project UI primitives and store-backed selectors.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
|
@ -10,10 +11,10 @@ import { useStore } from '@renderer/store';
|
|||
import { selectTeamDataForName } from '@renderer/store/slices/teamSlice';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import { isTaskBlocked, resolveTaskGraphColumn } from '../utils/taskGraphSemantics';
|
||||
import { isTaskBlocked, resolveTaskGraphColumn } from '../../core/domain/taskGraphSemantics';
|
||||
|
||||
import type { GraphNode } from '@claude-teams/agent-graph';
|
||||
import type { KanbanColumnId, TeamTask, TeamTaskWithKanban } from '@shared/types';
|
||||
import type { KanbanColumnId, TeamTask } from '@shared/types';
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -119,8 +120,8 @@ export const GraphTaskCard = ({
|
|||
const columnId = resolveColumn(task);
|
||||
const taskWithKanban = task;
|
||||
|
||||
const closeAct = (fn?: (id: string) => void) => (taskId: string) => {
|
||||
fn?.(taskId);
|
||||
const closeAct = (fn?: (id: string) => void) => (nextTaskId: string) => {
|
||||
fn?.(nextTaskId);
|
||||
onClose();
|
||||
};
|
||||
|
||||
|
|
@ -9,13 +9,13 @@ import { GraphView } from '@claude-teams/agent-graph';
|
|||
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost';
|
||||
import { useStore } from '@renderer/store';
|
||||
|
||||
import { useTeamGraphAdapter } from '../adapters/useTeamGraphAdapter';
|
||||
import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog';
|
||||
import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter';
|
||||
|
||||
import { GraphActivityHud } from './GraphActivityHud';
|
||||
import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover';
|
||||
import { GraphNodePopover } from './GraphNodePopover';
|
||||
import { GraphProvisioningHud } from './GraphProvisioningHud';
|
||||
import { useGraphCreateTaskDialog } from './useGraphCreateTaskDialog';
|
||||
|
||||
import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph';
|
||||
import type {
|
||||
|
|
@ -9,15 +9,15 @@ import { GraphView } from '@claude-teams/agent-graph';
|
|||
import { TeamSidebarHost } from '@renderer/components/team/sidebar/TeamSidebarHost';
|
||||
import { useStore } from '@renderer/store';
|
||||
|
||||
import { useTeamGraphAdapter } from '../adapters/useTeamGraphAdapter';
|
||||
import { useGraphCreateTaskDialog } from '../hooks/useGraphCreateTaskDialog';
|
||||
import { useTeamGraphAdapter } from '../hooks/useTeamGraphAdapter';
|
||||
|
||||
import { GraphActivityHud } from './GraphActivityHud';
|
||||
import { GraphBlockingEdgePopover } from './GraphBlockingEdgePopover';
|
||||
import { GraphNodePopover } from './GraphNodePopover';
|
||||
import { GraphProvisioningHud } from './GraphProvisioningHud';
|
||||
import { useGraphCreateTaskDialog } from './useGraphCreateTaskDialog';
|
||||
|
||||
import type { GraphDomainRef, GraphEventPort, GraphNode } from '@claude-teams/agent-graph';
|
||||
import type { GraphDomainRef, GraphEventPort } from '@claude-teams/agent-graph';
|
||||
import type {
|
||||
MemberActivityFilter,
|
||||
MemberDetailTab,
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
* Uses CSS display-toggle to keep all tabs mounted (preserving state).
|
||||
*/
|
||||
|
||||
import { TeamGraphTab } from '@features/agent-graph/renderer';
|
||||
import { TabUIProvider } from '@renderer/contexts/TabUIContext';
|
||||
import { TeamGraphTab } from '@renderer/features/agent-graph/ui/TeamGraphTab';
|
||||
|
||||
import { DashboardView } from '../dashboard/DashboardView';
|
||||
import { ExtensionStoreView } from '../extensions/ExtensionStoreView';
|
||||
|
|
|
|||
|
|
@ -72,15 +72,15 @@ import { TrashDialog } from './kanban/TrashDialog';
|
|||
import { MemberDetailDialog } from './members/MemberDetailDialog';
|
||||
import { type MemberActivityFilter, type MemberDetailTab } from './members/memberDetailTypes';
|
||||
|
||||
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
|
||||
import type { AddMemberEntry } from './dialogs/AddMemberDialog';
|
||||
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
|
||||
import type { ComponentProps, CSSProperties } from 'react';
|
||||
|
||||
const ProjectEditorOverlay = lazy(() =>
|
||||
import('./editor/ProjectEditorOverlay').then((m) => ({ default: m.ProjectEditorOverlay }))
|
||||
);
|
||||
const TeamGraphOverlay = lazy(() =>
|
||||
import('@renderer/features/agent-graph/ui/TeamGraphOverlay').then((m) => ({
|
||||
import('@features/agent-graph/renderer').then((m) => ({
|
||||
default: m.TeamGraphOverlay,
|
||||
}))
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { buildInlineActivityEntries } from '@features/agent-graph/renderer';
|
||||
import { Button } from '@renderer/components/ui/button';
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
||||
import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries';
|
||||
import { useMemberStats } from '@renderer/hooks/useMemberStats';
|
||||
import { isLeadMember } from '@shared/utils/leadDetection';
|
||||
import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { buildInlineActivityEntries } from '@features/agent-graph/renderer';
|
||||
import { api } from '@renderer/api';
|
||||
import { ActivityItem } from '@renderer/components/team/activity/ActivityItem';
|
||||
import {
|
||||
|
|
@ -8,7 +9,6 @@ import {
|
|||
} from '@renderer/components/team/activity/activityMessageContext';
|
||||
import { MessageExpandDialog } from '@renderer/components/team/activity/MessageExpandDialog';
|
||||
import { Button } from '@renderer/components/ui/button';
|
||||
import { buildInlineActivityEntries } from '@renderer/features/agent-graph/utils/buildInlineActivityEntries';
|
||||
import { useTeamMessagesRead } from '@renderer/hooks/useTeamMessagesRead';
|
||||
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
|
||||
import { filterTeamMessages } from '@renderer/utils/teamMessageFiltering';
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* agent-graph feature — public API.
|
||||
* Only exports UI components and adapter types.
|
||||
*/
|
||||
|
||||
export { TeamGraphAdapter } from './adapters/TeamGraphAdapter';
|
||||
export type { TeamGraphOverlayProps } from './ui/TeamGraphOverlay';
|
||||
export { TeamGraphOverlay } from './ui/TeamGraphOverlay';
|
||||
export { TeamGraphTab } from './ui/TeamGraphTab';
|
||||
|
|
@ -2,7 +2,7 @@ import React, { act } from 'react';
|
|||
import { createRoot } from 'react-dom/client';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { GraphActivityHud } from '@renderer/features/agent-graph/ui/GraphActivityHud';
|
||||
import { GraphActivityHud } from '@features/agent-graph/renderer/ui/GraphActivityHud';
|
||||
|
||||
import type { GraphNode } from '@claude-teams/agent-graph';
|
||||
import type { InboxMessage } from '@shared/types/team';
|
||||
|
|
@ -17,16 +17,22 @@ const teamState = {
|
|||
tasks: [],
|
||||
messages: [],
|
||||
},
|
||||
teamDataCacheByName: {
|
||||
'demo-team': {
|
||||
members: [
|
||||
{ name: 'team-lead', agentType: 'team-lead' },
|
||||
{ name: 'jack', agentType: 'developer' },
|
||||
],
|
||||
tasks: [],
|
||||
messages: [],
|
||||
},
|
||||
} as Record<string, { members: Array<Record<string, unknown>>; tasks: unknown[]; messages: unknown[] }>,
|
||||
teamDataCacheByName: new Map<
|
||||
string,
|
||||
{ members: Record<string, unknown>[]; tasks: unknown[]; messages: unknown[] }
|
||||
>([
|
||||
[
|
||||
'demo-team',
|
||||
{
|
||||
members: [
|
||||
{ name: 'team-lead', agentType: 'team-lead' },
|
||||
{ name: 'jack', agentType: 'developer' },
|
||||
],
|
||||
tasks: [],
|
||||
messages: [],
|
||||
},
|
||||
],
|
||||
]),
|
||||
teams: [],
|
||||
};
|
||||
|
||||
|
|
@ -38,7 +44,7 @@ vi.mock('@renderer/store', () => ({
|
|||
|
||||
vi.mock('@renderer/store/slices/teamSlice', () => ({
|
||||
selectTeamDataForName: (_state: typeof teamState, teamName: string) =>
|
||||
teamState.teamDataCacheByName[teamName] ??
|
||||
teamState.teamDataCacheByName.get(teamName) ??
|
||||
(teamState.selectedTeamName === teamName ? teamState.selectedTeamData : null),
|
||||
}));
|
||||
|
||||
|
|
@ -79,7 +85,7 @@ vi.mock('@renderer/components/team/activity/activityMessageContext', () => ({
|
|||
resolveMessageRenderProps: () => ({}),
|
||||
}));
|
||||
|
||||
vi.mock('@renderer/features/agent-graph/utils/buildInlineActivityEntries', () => ({
|
||||
vi.mock('@features/agent-graph/core/domain/buildInlineActivityEntries', () => ({
|
||||
buildInlineActivityEntries: (...args: unknown[]) => buildInlineActivityEntries(...args),
|
||||
getGraphLeadMemberName: () => 'team-lead',
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import React, { act } from 'react';
|
|||
import { createRoot } from 'react-dom/client';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { GraphBlockingEdgePopover } from '@features/agent-graph/renderer/ui/GraphBlockingEdgePopover';
|
||||
import { useStore } from '@renderer/store';
|
||||
import { GraphBlockingEdgePopover } from '@renderer/features/agent-graph/ui/GraphBlockingEdgePopover';
|
||||
|
||||
import type { GraphEdge, GraphNode } from '@claude-teams/agent-graph';
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ vi.mock('@renderer/components/ui/button', () => ({
|
|||
React.createElement('button', { type: 'button' }, children),
|
||||
}));
|
||||
|
||||
vi.mock('@renderer/features/agent-graph/ui/GraphTaskCard', () => ({
|
||||
vi.mock('@features/agent-graph/renderer/ui/GraphTaskCard', () => ({
|
||||
GraphTaskCard: () => React.createElement('div', null, 'task-card'),
|
||||
}));
|
||||
|
||||
import { GraphNodePopover } from '@renderer/features/agent-graph/ui/GraphNodePopover';
|
||||
import { GraphNodePopover } from '@features/agent-graph/renderer/ui/GraphNodePopover';
|
||||
|
||||
import type { GraphNode } from '@claude-teams/agent-graph';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import React, { act } from 'react';
|
|||
import { createRoot } from 'react-dom/client';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { GraphProvisioningHud } from '@features/agent-graph/renderer/ui/GraphProvisioningHud';
|
||||
|
||||
const hookState = {
|
||||
presentation: null as
|
||||
| {
|
||||
|
|
@ -44,8 +46,6 @@ vi.mock('@renderer/components/team/TeamProvisioningPanel', () => ({
|
|||
),
|
||||
}));
|
||||
|
||||
import { GraphProvisioningHud } from '@renderer/features/agent-graph/ui/GraphProvisioningHud';
|
||||
|
||||
const placement = { x: 120, y: 80, scale: 1, visible: true };
|
||||
|
||||
describe('GraphProvisioningHud', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { TeamGraphAdapter } from '@renderer/features/agent-graph/adapters/TeamGraphAdapter';
|
||||
import { TeamGraphAdapter } from '@features/agent-graph/renderer/adapters/TeamGraphAdapter';
|
||||
|
||||
import type { InboxMessage, TeamData, TeamTaskWithKanban } from '@shared/types/team';
|
||||
import type { GraphDataPort } from '@claude-teams/agent-graph';
|
||||
|
|
@ -459,7 +459,7 @@ describe('TeamGraphAdapter particles', () => {
|
|||
const graph = adapter.adapt(next, 'my-team');
|
||||
|
||||
expect(graph.particles).toHaveLength(2);
|
||||
expect(graph.particles.map((particle) => particle.kind).sort()).toEqual([
|
||||
expect(graph.particles.map((particle) => particle.kind).toSorted((a, b) => a.localeCompare(b))).toEqual([
|
||||
'inbox_message',
|
||||
'task_comment',
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|||
import {
|
||||
buildInlineActivityEntries,
|
||||
getGraphLeadMemberName,
|
||||
} from '@renderer/features/agent-graph/utils/buildInlineActivityEntries';
|
||||
} from '@features/agent-graph/core/domain/buildInlineActivityEntries';
|
||||
|
||||
import type { InboxMessage, TeamData, TeamTaskWithKanban } from '@shared/types/team';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|||
import {
|
||||
collapseOverflowStacks,
|
||||
collapseOverflowStacksWithMeta,
|
||||
} from '@renderer/features/agent-graph/utils/collapseOverflowStacks';
|
||||
} from '@features/agent-graph/core/domain/collapseOverflowStacks';
|
||||
|
||||
import type { GraphNode } from '@claude-teams/agent-graph';
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue