feat(FN-1850): aggregate projects across connected nodes

- Add /api/projects/across-nodes to merge local projects with online remote node project lists
- Expose fetchProjectsAcrossNodes and ProjectInfoWithSource, and switch useProjects to consume cross-node data
- Update ProjectOverview and ProjectCard with node badges, node count stats, and a node filter dropdown plus responsive styles
- Add server and dashboard test coverage for cross-node aggregation, filtering, and hook behavior changes
- Include a changeset for @gsxdsm/fusion minor release and preserve stale-channel SSE reconnect guards during merge
This commit is contained in:
Fusion
2026-04-18 03:08:00 -07:00
committed by gsxdsm
parent 76123c9538
commit 53babb3524
14 changed files with 1674 additions and 55 deletions

View File

@@ -1,16 +1,17 @@
import { useState, useEffect, useCallback, useRef } from "react";
import type { ProjectInfo } from "../api";
import {
fetchProjects,
fetchProjectsAcrossNodes,
registerProject,
unregisterProject,
updateProject,
type ProjectCreateInput,
type ProjectInfoWithSource,
} from "../api";
export interface UseProjectsResult {
/** List of all registered projects */
projects: ProjectInfo[];
/** List of all registered projects (local + remote) */
projects: ProjectInfoWithSource[];
/** Loading state for initial fetch */
loading: boolean;
/** Error message if fetch failed */
@@ -35,7 +36,7 @@ const VISIBILITY_REFRESH_DEBOUNCE_MS = 1000;
* Provides optimistic updates for UI responsiveness.
*/
export function useProjects(): UseProjectsResult {
const [projects, setProjects] = useState<ProjectInfo[]>([]);
const [projects, setProjects] = useState<ProjectInfoWithSource[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -44,7 +45,7 @@ export function useProjects(): UseProjectsResult {
const refresh = useCallback(async () => {
try {
setError(null);
const data = await fetchProjects();
const data = await fetchProjectsAcrossNodes();
setProjects(data);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to fetch projects");
@@ -59,7 +60,7 @@ export function useProjects(): UseProjectsResult {
async function load() {
setLoading(true);
try {
const data = await fetchProjects();
const data = await fetchProjectsAcrossNodes();
if (!cancelled) {
setProjects(data);
setError(null);