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
35 changes: 28 additions & 7 deletions packages/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import {
currentSpaceSessionIdSignal,
currentSpaceTaskIdSignal,
currentSpaceViewModeSignal,
currentSpaceConfigureTabSignal,
currentSpaceTasksFilterTabSignal,
currentSpaceTaskViewTabSignal,
navSectionSignal,
} from './lib/signals.ts';
import { initSessionStatusTracking } from './lib/session-status.ts';
Expand All @@ -43,6 +46,7 @@ import {
navigateToSpaceAgent,
navigateToSpaceSession,
navigateToSpaceTask,
navigateToSettings,
createSessionPath,
createRoomPath,
createRoomAgentPath,
Expand Down Expand Up @@ -124,6 +128,9 @@ export function App() {
const spaceSessionId = currentSpaceSessionIdSignal.value;
const spaceTaskId = currentSpaceTaskIdSignal.value;
const spaceViewMode = currentSpaceViewModeSignal.value;
const spaceConfigureTab = currentSpaceConfigureTabSignal.value;
const spaceTasksFilterTab = currentSpaceTasksFilterTabSignal.value;
const spaceTaskViewTab = currentSpaceTaskViewTabSignal.value;
const navSection = navSectionSignal.value;
const currentPath = window.location.pathname;
// Detect agent routes: new signal-based detection, with legacy session ID fallback
Expand All @@ -139,17 +146,27 @@ export function App() {
const expectedPath = sessionId
? createSessionPath(sessionId)
: spaceTaskId && spaceId
? createSpaceTaskPath(spaceId, spaceTaskId)
? createSpaceTaskPath(
spaceId,
spaceTaskId,
spaceTaskViewTab !== 'thread' ? spaceTaskViewTab : undefined
)
: isSpaceAgentRoute
? createSpaceAgentPath(spaceId)
: spaceSessionId && spaceId
? createSpaceSessionPath(spaceId, spaceSessionId)
: spaceId && spaceViewMode === 'sessions'
? createSpaceSessionsPath(spaceId)
: spaceId && spaceViewMode === 'tasks'
? createSpaceTasksPath(spaceId)
? createSpaceTasksPath(
spaceId,
spaceTasksFilterTab !== 'active' ? spaceTasksFilterTab : undefined
)
: spaceId && spaceViewMode === 'configure'
? createSpaceConfigurePath(spaceId)
? createSpaceConfigurePath(
spaceId,
spaceConfigureTab !== 'agents' ? spaceConfigureTab : undefined
)
: spaceId
? createSpacePath(spaceId)
: roomTaskId && roomId
Expand All @@ -166,25 +183,27 @@ export function App() {
? '/spaces'
: navSection === 'chats'
? '/sessions'
: '/';
: navSection === 'settings'
? '/settings'
: '/';

// Only update URL if it's out of sync
// This prevents unnecessary history updates and loops
if (currentPath !== expectedPath) {
if (sessionId) {
navigateToSession(sessionId, true); // replace=true to avoid polluting history
} else if (spaceTaskId && spaceId) {
navigateToSpaceTask(spaceId, spaceTaskId, true);
navigateToSpaceTask(spaceId, spaceTaskId, undefined, true);
} else if (isSpaceAgentRoute) {
navigateToSpaceAgent(spaceId, true);
} else if (spaceSessionId && spaceId) {
navigateToSpaceSession(spaceId, spaceSessionId, true);
} else if (spaceId && spaceViewMode === 'sessions') {
navigateToSpaceSessions(spaceId, true);
} else if (spaceId && spaceViewMode === 'tasks') {
navigateToSpaceTasks(spaceId, true);
navigateToSpaceTasks(spaceId, undefined, true);
} else if (spaceId && spaceViewMode === 'configure') {
navigateToSpaceConfigure(spaceId, true);
navigateToSpaceConfigure(spaceId, undefined, true);
} else if (spaceId) {
navigateToSpace(spaceId, true);
} else if (roomTaskId && roomId) {
Expand All @@ -201,6 +220,8 @@ export function App() {
navigateToSpacesPage(true);
} else if (navSection === 'chats') {
// Already at /sessions or no navigation needed
} else if (navSection === 'settings') {
navigateToSettings(true);
} else {
navigateToHome(true);
}
Expand Down
10 changes: 2 additions & 8 deletions packages/web/src/components/inbox/Inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { useEffect, useState } from 'preact/hooks';
import { inboxStore, type InboxTask } from '../../lib/inbox-store.ts';
import { Spinner } from '../ui/Spinner.tsx';
import { toast } from '../../lib/toast.ts';
import {
currentRoomIdSignal,
currentRoomTaskIdSignal,
navSectionSignal,
} from '../../lib/signals.ts';
import { navigateToRoomTask } from '../../lib/router.ts';

function InboxTaskCard({
item,
Expand All @@ -25,9 +21,7 @@ function InboxTaskCard({
const anyApproving = approvingId !== null;

const handleView = () => {
navSectionSignal.value = 'rooms';
currentRoomIdSignal.value = item.roomId;
currentRoomTaskIdSignal.value = item.task.id;
navigateToRoomTask(item.roomId, item.task.id);
};

const handleApprove = async () => {
Expand Down
9 changes: 7 additions & 2 deletions packages/web/src/components/space/SpaceConfigurePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { lazy, Suspense } from 'preact/compat';
import type { Space } from '@neokai/shared';
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@neokai/ui';
import { spaceStore } from '../../lib/space-store';
import { currentSpaceConfigureTabSignal, currentSpaceIdSignal } from '../../lib/signals';
import { navigateToSpaceConfigure } from '../../lib/router';
import { cn } from '../../lib/utils';

const SpaceAgentList = lazy(() =>
Expand Down Expand Up @@ -51,7 +53,8 @@ export function SpaceConfigurePage({ space }: SpaceConfigurePageProps) {
spaceStore.ensureConfigData().catch(() => {});
spaceStore.ensureNodeExecutions().catch(() => {});
}, [space.id]);
const [activeTab, setActiveTab] = useState<ConfigureTab>('agents');
const activeTab = currentSpaceConfigureTabSignal.value;
const spaceId = currentSpaceIdSignal.value ?? '';
/** null = list view; 'new' = create editor; <id> = edit editor */
const [workflowEditId, setWorkflowEditId] = useState<string | null>(null);

Expand Down Expand Up @@ -80,7 +83,9 @@ export function SpaceConfigurePage({ space }: SpaceConfigurePageProps) {
{!showWorkflowEditor && (
<TabGroup
selectedIndex={selectedIndex}
onChange={(index: number) => setActiveTab(CONFIGURE_TABS[index]?.id ?? 'agents')}
onChange={(index: number) =>
navigateToSpaceConfigure(spaceId, CONFIGURE_TABS[index]?.id ?? 'agents')
}
>
<TabList
class="flex items-center gap-6 border-b border-dark-700 px-6"
Expand Down
35 changes: 17 additions & 18 deletions packages/web/src/components/space/SpaceTaskPane.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'preact/hooks';
import { spaceStore } from '../../lib/space-store';
import { spaceOverlaySessionIdSignal, spaceOverlayAgentNameSignal } from '../../lib/signals';
import { pushOverlayHistory, navigateToSpaceTask } from '../../lib/router';
import { currentSpaceTaskViewTabSignal, currentSpaceIdSignal } from '../../lib/signals';
import type {
SpaceTaskActivityMember,
SpaceTaskActivityState,
Expand Down Expand Up @@ -84,11 +85,11 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
const [threadSendError, setThreadSendError] = useState<string | null>(null);
const [sendingThread, setSendingThread] = useState(false);
const [statusTransitioning, setStatusTransitioning] = useState(false);
const [activeView, setActiveView] = useState<'thread' | 'canvas' | 'artifacts'>('thread');
const activeView = currentSpaceTaskViewTabSignal.value;
const _spaceId = currentSpaceIdSignal.value ?? '';

useEffect(() => {
setThreadSendError(null);
setActiveView('thread');
}, [taskId]);

useEffect(() => {
Expand Down Expand Up @@ -174,11 +175,11 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)

useEffect(() => {
if (activeView === 'canvas' && !canShowCanvasTab) {
setActiveView('thread');
navigateToSpaceTask(_spaceId, taskId, 'thread');
return;
}
if (activeView === 'artifacts' && !canShowArtifactsTab) {
setActiveView('thread');
navigateToSpaceTask(_spaceId, taskId, 'thread');
}
}, [activeView, canShowCanvasTab, canShowArtifactsTab]);

Expand All @@ -198,23 +199,20 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
(m) => m.kind === 'node_agent' && agentDisplayNames.includes(m.label)
);
if (nodeMember) {
spaceOverlayAgentNameSignal.value = nodeMember.label;
spaceOverlaySessionIdSignal.value = nodeMember.sessionId;
pushOverlayHistory(nodeMember.sessionId, nodeMember.label);
return;
}

// Fall back to the task agent session (coordinator/leader)
const taskAgentMember = activityMembers.find((m) => m.kind === 'task_agent');
if (taskAgentMember) {
spaceOverlayAgentNameSignal.value = taskAgentMember.label;
spaceOverlaySessionIdSignal.value = taskAgentMember.sessionId;
pushOverlayHistory(taskAgentMember.sessionId, taskAgentMember.label);
return;
}

// Last resort: use the task’s own agentSessionId
if (agentSessionId) {
spaceOverlayAgentNameSignal.value = agentActionLabel;
spaceOverlaySessionIdSignal.value = agentSessionId;
pushOverlayHistory(agentSessionId, agentActionLabel);
}
};

Expand Down Expand Up @@ -262,8 +260,7 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
...activityMembers.map((member) => ({
label: `Open ${member.label} (${ACTIVITY_STATE_LABELS[member.state]})`,
onClick: () => {
spaceOverlayAgentNameSignal.value = member.label;
spaceOverlaySessionIdSignal.value = member.sessionId;
pushOverlayHistory(member.sessionId, member.label);
},
}))
);
Expand Down Expand Up @@ -343,7 +340,7 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
<div class="flex items-center gap-1 rounded-3xl border border-dark-700 bg-dark-800/60 p-1 backdrop-blur-sm">
<button
type="button"
onClick={() => setActiveView('thread')}
onClick={() => navigateToSpaceTask(_spaceId, taskId, 'thread')}
class={cn(
'px-2.5 py-1 text-xs font-medium rounded-2xl transition-all',
activeView === 'thread'
Expand All @@ -360,11 +357,11 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
type="button"
onClick={() => {
if (activeView === 'canvas') {
setActiveView('thread');
navigateToSpaceTask(_spaceId, taskId, 'thread');
return;
}
spaceStore.ensureNodeExecutions().catch(() => {});
setActiveView('canvas');
navigateToSpaceTask(_spaceId, taskId, 'canvas');
}}
class={cn(
'px-2.5 py-1 text-xs font-medium rounded-2xl transition-all',
Expand All @@ -382,7 +379,9 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
<button
type="button"
onClick={() =>
setActiveView((view) => (view === 'artifacts' ? 'thread' : 'artifacts'))
currentSpaceTaskViewTabSignal.value === 'artifacts'
? navigateToSpaceTask(_spaceId, taskId, 'thread')
: navigateToSpaceTask(_spaceId, taskId, 'artifacts')
}
class={cn(
'px-2.5 py-1 text-xs font-medium rounded-2xl transition-all',
Expand Down Expand Up @@ -413,7 +412,7 @@ export function SpaceTaskPane({ taskId, spaceId, onClose }: SpaceTaskPaneProps)
<TaskArtifactsPanel
runId={task.workflowRunId}
taskId={task.id}
onClose={() => setActiveView('thread')}
onClose={() => navigateToSpaceTask(_spaceId, taskId, 'thread')}
class="h-full"
/>
) : (
Expand Down
21 changes: 13 additions & 8 deletions packages/web/src/components/space/SpaceTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { useEffect, useMemo, useState } from 'preact/hooks';
import { spaceStore } from '../../lib/space-store';
import type { SpaceBlockReason, SpaceTask, SpaceTaskStatus } from '@neokai/shared';
import { getRelativeTime } from '../../lib/utils';
import { currentSpaceTasksFilterSignal } from '../../lib/signals';
import {
currentSpaceTasksFilterSignal,
currentSpaceTasksFilterTabSignal,
currentSpaceIdSignal,
} from '../../lib/signals';
import { navigateToSpaceTasks } from '../../lib/router';

type TaskFilterTab = 'action' | 'active' | 'completed' | 'archived';

Expand Down Expand Up @@ -304,8 +309,8 @@ export function SpaceTasks({ spaceId: _spaceId, onSelectTask }: SpaceTasksProps)
// land on the Action tab so the filtered list is non-empty by default.
// Otherwise keep the default of "Active" — matches historical behavior for
// users coming in without a filter.
const initialTab: TaskFilterTab = preFilter ? 'action' : 'active';
const [activeTab, setActiveTab] = useState<TaskFilterTab>(initialTab);
const activeTab: TaskFilterTab = currentSpaceTasksFilterTabSignal.value;
const spaceId = currentSpaceIdSignal.value ?? '';
const [activeFilter, setActiveFilter] = useState<'awaiting_completion_action' | null>(preFilter);

// Sync from the signal once, then clear it so the filter doesn't re-apply on
Expand All @@ -315,7 +320,7 @@ export function SpaceTasks({ spaceId: _spaceId, onSelectTask }: SpaceTasksProps)
useEffect(() => {
if (preFilter) {
setActiveFilter(preFilter);
setActiveTab('action');
navigateToSpaceTasks(spaceId, 'action');
currentSpaceTasksFilterSignal.value = null;
}
}, []);
Expand Down Expand Up @@ -385,27 +390,27 @@ export function SpaceTasks({ spaceId: _spaceId, onSelectTask }: SpaceTasksProps)
label="Action"
count={counts.action}
isActive={activeTab === 'action'}
onClick={() => setActiveTab('action')}
onClick={() => navigateToSpaceTasks(spaceId, 'action')}
variant="amber"
/>
<TabButton
label="Active"
count={counts.active}
isActive={activeTab === 'active'}
onClick={() => setActiveTab('active')}
onClick={() => navigateToSpaceTasks(spaceId, 'active')}
/>
<TabButton
label="Completed"
count={counts.completed}
isActive={activeTab === 'completed'}
onClick={() => setActiveTab('completed')}
onClick={() => navigateToSpaceTasks(spaceId, 'completed')}
variant="green"
/>
<TabButton
label="Archived"
count={counts.archived}
isActive={activeTab === 'archived'}
onClick={() => setActiveTab('archived')}
onClick={() => navigateToSpaceTasks(spaceId, 'archived')}
variant="gray"
/>
</div>
Expand Down
Loading