From fe75ea5c45ec7279bdbb478e0f48621822490659 Mon Sep 17 00:00:00 2001 From: Corentin Date: Mon, 18 May 2026 09:20:38 +0000 Subject: [PATCH] fix(database-view): send tableId to bridge and correct SSE path useViewData omitted the tableId query param required by the bridge GET /views/:id/data route -> 400 'tableId query param required' and a blank 'Could not load view'. The SSE consumer hit /api/v1/events/sse but the bridge mounts the stream at /api/events -> 404 reconnect loop. Thread tableId through ViewDataParams and all five callers; point the SSE URL at /api/events. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../database-view/hooks/use-database-realtime-updates.ts | 7 +++++-- .../acadenice/database-view/hooks/use-view-data.ts | 7 +++++-- .../database-view/renderers/calendar-renderer.tsx | 1 + .../acadenice/database-view/renderers/kanban-renderer.tsx | 1 + .../acadenice/database-view/renderers/table-renderer.tsx | 1 + .../database-view/renderers/timeline-renderer.tsx | 1 + .../database-view/slash-command/insert-database-modal.tsx | 1 + .../acadenice/database-view/types/database-view.types.ts | 3 +++ 8 files changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/client/src/features/acadenice/database-view/hooks/use-database-realtime-updates.ts b/apps/client/src/features/acadenice/database-view/hooks/use-database-realtime-updates.ts index 424dcad6..dec198fa 100644 --- a/apps/client/src/features/acadenice/database-view/hooks/use-database-realtime-updates.ts +++ b/apps/client/src/features/acadenice/database-view/hooks/use-database-realtime-updates.ts @@ -6,7 +6,7 @@ import { resolveBridgeUrl } from "../services/bridge-client"; /** * SSE consumer for realtime row/view updates from the bridge. * - * Connects to `GET /api/v1/events/sse?tables=&views=` and + * Connects to `GET /api/events/sse?tables=&views=` and * listens for events whose type starts with `row.` or `view.`. On match, it * invalidates the React Query cache for the affected view so the table * re-fetches silently. @@ -46,7 +46,10 @@ export function useDatabaseRealtimeUpdates( if (!tableId || !viewId) return; const url = resolveBridgeUrl(bridgeUrl); - const sseUrl = `${url}/api/v1/events/sse?tables=${encodeURIComponent(tableId)}&views=${encodeURIComponent(viewId)}`; + // The bridge mounts the SSE router at /api/events (NOT under /api/v1) on + // purpose, to keep it out of the v1 mutation rate-limiter. See bridge + // src/index.ts: app.route('/api', eventsRouter) + eventsRoutes '/events'. + const sseUrl = `${url}/api/events/sse?tables=${encodeURIComponent(tableId)}&views=${encodeURIComponent(viewId)}`; let retryTimeout: ReturnType | null = null; diff --git a/apps/client/src/features/acadenice/database-view/hooks/use-view-data.ts b/apps/client/src/features/acadenice/database-view/hooks/use-view-data.ts index 5a0583ea..06021c4b 100644 --- a/apps/client/src/features/acadenice/database-view/hooks/use-view-data.ts +++ b/apps/client/src/features/acadenice/database-view/hooks/use-view-data.ts @@ -34,6 +34,7 @@ export interface UseViewDataResult { */ export function useViewData({ viewId, + tableId, bridgeUrl, page = 1, size = 50, @@ -42,11 +43,13 @@ export function useViewData({ return useQuery({ queryKey: viewDataQueryKey(viewId, page, size, url), - enabled: Boolean(viewId), + enabled: Boolean(viewId) && Boolean(tableId), queryFn: async () => { const client = getBridgeClient(url); + // tableId is mandatory: the bridge route GET /views/:viewId/data returns + // 400 "tableId query param required" without it. const res = await (client.get(`/api/v1/views/${viewId}/data`, { - params: { page, size }, + params: { page, size, tableId }, }) as unknown as Promise); // Normalise: bridge may return top-level array or wrapped envelope. diff --git a/apps/client/src/features/acadenice/database-view/renderers/calendar-renderer.tsx b/apps/client/src/features/acadenice/database-view/renderers/calendar-renderer.tsx index b38e92b8..e53edec4 100644 --- a/apps/client/src/features/acadenice/database-view/renderers/calendar-renderer.tsx +++ b/apps/client/src/features/acadenice/database-view/renderers/calendar-renderer.tsx @@ -102,6 +102,7 @@ export function CalendarRenderer({ tableId, viewId, bridgeUrl }: CalendarRendere const { data, isLoading, isError, error, refetch } = useViewData({ viewId, + tableId, bridgeUrl, page: 1, size: PAGE_SIZE, diff --git a/apps/client/src/features/acadenice/database-view/renderers/kanban-renderer.tsx b/apps/client/src/features/acadenice/database-view/renderers/kanban-renderer.tsx index f2f609ac..863932c7 100644 --- a/apps/client/src/features/acadenice/database-view/renderers/kanban-renderer.tsx +++ b/apps/client/src/features/acadenice/database-view/renderers/kanban-renderer.tsx @@ -280,6 +280,7 @@ export function KanbanRenderer({ tableId, viewId, bridgeUrl }: KanbanRendererPro const { data, isLoading, isError, error, refetch } = useViewData({ viewId, + tableId, bridgeUrl, page: 1, size: PAGE_SIZE, diff --git a/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx b/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx index d15098ef..2a83f333 100644 --- a/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx +++ b/apps/client/src/features/acadenice/database-view/renderers/table-renderer.tsx @@ -173,6 +173,7 @@ export function TableRenderer({ tableId, viewId, bridgeUrl }: TableRendererProps const { data, isLoading, isError, error, refetch } = useViewData({ viewId, + tableId, bridgeUrl, page, size: PAGE_SIZE, diff --git a/apps/client/src/features/acadenice/database-view/renderers/timeline-renderer.tsx b/apps/client/src/features/acadenice/database-view/renderers/timeline-renderer.tsx index bb0ecbdb..beb6d4ef 100644 --- a/apps/client/src/features/acadenice/database-view/renderers/timeline-renderer.tsx +++ b/apps/client/src/features/acadenice/database-view/renderers/timeline-renderer.tsx @@ -261,6 +261,7 @@ export function TimelineRenderer({ tableId, viewId, bridgeUrl }: TimelineRendere const { data, isLoading: dataLoading, isError: dataError, error, refetch } = useViewData({ viewId, + tableId, bridgeUrl, page: 1, size: PAGE_SIZE, diff --git a/apps/client/src/features/acadenice/database-view/slash-command/insert-database-modal.tsx b/apps/client/src/features/acadenice/database-view/slash-command/insert-database-modal.tsx index c02e0902..a43af413 100644 --- a/apps/client/src/features/acadenice/database-view/slash-command/insert-database-modal.tsx +++ b/apps/client/src/features/acadenice/database-view/slash-command/insert-database-modal.tsx @@ -118,6 +118,7 @@ export function InsertDatabaseModal({ isError: fieldsError, } = useViewData({ viewId: selectedView?.id ?? "", + tableId: selectedTable?.id ?? "", bridgeUrl, page: 1, size: 1, diff --git a/apps/client/src/features/acadenice/database-view/types/database-view.types.ts b/apps/client/src/features/acadenice/database-view/types/database-view.types.ts index db5abb8d..aea9d2ca 100644 --- a/apps/client/src/features/acadenice/database-view/types/database-view.types.ts +++ b/apps/client/src/features/acadenice/database-view/types/database-view.types.ts @@ -71,6 +71,9 @@ export interface BridgeViewDataResponse { /** Paginated fetch params for the view-data hook. */ export interface ViewDataParams { viewId: string; + // Required by the bridge: GET /views/:viewId/data needs tableId to build + // Row instances (Baserow does not echo the tableId in listRows responses). + tableId: string; bridgeUrl?: string | null; page?: number; size?: number;