diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index b5a1a256482..44a226bf84c 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -98,6 +98,8 @@
"@t3-oss/env-core": "^0.13.8",
"@tanstack/db": "0.6.5",
"@tanstack/electric-db-collection": "0.3.3",
+ "@tanstack/electron-db-sqlite-persistence": "0.1.9",
+ "@tanstack/node-db-sqlite-persistence": "0.1.9",
"@tanstack/react-db": "0.1.83",
"@tanstack/react-query": "^5.90.19",
"@tanstack/react-router": "^1.147.3",
diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts
index 3c0eabe04c6..d300f08b7bf 100644
--- a/apps/desktop/src/main/index.ts
+++ b/apps/desktop/src/main/index.ts
@@ -33,6 +33,10 @@ import { loadWebviewBrowserExtension } from "./lib/extensions";
import { getHostServiceCoordinator } from "./lib/host-service-coordinator";
import { localDb } from "./lib/local-db";
import { requestLocalNetworkAccess } from "./lib/local-network-permission";
+import {
+ initTanstackDbPersistence,
+ shutdownTanstackDbPersistence,
+} from "./lib/persistence/persistence";
import { ensureProjectIconsDir, getProjectIconPath } from "./lib/project-icons";
import { initSentry } from "./lib/sentry";
import {
@@ -207,6 +211,7 @@ app.on("before-quit", async (event) => {
isQuitting = true;
try {
getHostServiceCoordinator().releaseAll();
+ shutdownTanstackDbPersistence();
disposeTray();
} catch (error) {
console.error("[main] Cleanup during quit failed:", error);
@@ -345,6 +350,7 @@ if (!gotTheLock) {
setWorkspaceDockIcon();
initSentry();
await initAppState();
+ initTanstackDbPersistence();
await loadWebviewBrowserExtension();
diff --git a/apps/desktop/src/main/lib/persistence/persistence.ts b/apps/desktop/src/main/lib/persistence/persistence.ts
new file mode 100644
index 00000000000..fb2a34d4afc
--- /dev/null
+++ b/apps/desktop/src/main/lib/persistence/persistence.ts
@@ -0,0 +1,26 @@
+import { join } from "node:path";
+import { exposeElectronSQLitePersistence } from "@tanstack/electron-db-sqlite-persistence/main";
+import { createNodeSQLitePersistence } from "@tanstack/node-db-sqlite-persistence";
+import Database from "better-sqlite3";
+import { ipcMain } from "electron";
+import {
+ ensureSupersetHomeDirExists,
+ SUPERSET_HOME_DIR,
+} from "../app-environment";
+
+let dispose: (() => void) | null = null;
+let database: Database.Database | null = null;
+
+export function initTanstackDbPersistence(): void {
+ ensureSupersetHomeDirExists();
+ database = new Database(join(SUPERSET_HOME_DIR, "tanstack-db.sqlite"));
+ const persistence = createNodeSQLitePersistence({ database });
+ dispose = exposeElectronSQLitePersistence({ ipcMain, persistence });
+}
+
+export function shutdownTanstackDbPersistence(): void {
+ dispose?.();
+ dispose = null;
+ database?.close();
+ database = null;
+}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx
index 5062a4599fd..9611eef13ef 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx
@@ -1,4 +1,3 @@
-import { Spinner } from "@superset/ui/spinner";
import { useLiveQuery } from "@tanstack/react-db";
import { useNavigate } from "@tanstack/react-router";
import { useCallback, useEffect, useRef, useState } from "react";
@@ -78,7 +77,7 @@ export function TasksView({
storeSetSearch(searchQuery);
}, [searchQuery, storeSetSearch]);
- const { data: integrations, isLoading: isCheckingLinear } = useLiveQuery(
+ const { data: integrations } = useLiveQuery(
(q) =>
q
.from({ integrationConnections: collections.integrationConnections })
@@ -134,7 +133,7 @@ export function TasksView({
});
};
- const showLinearCTA = !isCheckingLinear && !isLinearConnected;
+ const showLinearCTA = integrations !== undefined && !isLinearConnected;
return (
@@ -153,11 +152,7 @@ export function TasksView({
/>
)}
- {isCheckingLinear ? (
-
-
-
- ) : showLinearCTA ? (
+ {showLinearCTA ? (
) : viewMode === "board" ? (
-
-
- );
- }
-
if (data.length === 0) {
return (
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx
index f2425da20b1..b49262bc15e 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx
@@ -1,4 +1,3 @@
-import { Spinner } from "@superset/ui/spinner";
import { useCallback, useEffect, useMemo } from "react";
import { HiCheckCircle } from "react-icons/hi2";
import type { TaskWithStatus } from "../../hooks/useTasksData";
@@ -25,7 +24,7 @@ export function TableContent({
onTaskClick,
onSelectionChange,
}: TableContentProps) {
- const { table, isLoading, slugColumnWidth, rowSelection, setRowSelection } =
+ const { table, slugColumnWidth, rowSelection, setRowSelection } =
useTasksTable({
filterTab,
searchQuery,
@@ -44,14 +43,6 @@ export function TableContent({
onSelectionChange?.(selectedTasks, clearSelection);
}, [selectedTasks, clearSelection, onSelectionChange]);
- if (isLoading) {
- return (
-
-
-
- );
- }
-
if (table.getRowModel().rows.length === 0) {
return (
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx
index 8c632d1fe64..e70ae23cdf9 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx
@@ -29,11 +29,10 @@ export function useTasksData({
}: UseTasksDataParams): {
data: TaskWithStatus[];
allStatuses: SelectTaskStatus[];
- isLoading: boolean;
} {
const collections = useCollections();
- const { data: allData, isLoading } = useLiveQuery(
+ const { data: allData } = useLiveQuery(
(q) =>
q
.from({ tasks: collections.tasks })
@@ -52,7 +51,7 @@ export function useTasksData({
[collections],
);
- const { data: statusData, isLoading: isStatusesLoading } = useLiveQuery(
+ const { data: statusData } = useLiveQuery(
(q) =>
q
.from({ taskStatuses: collections.taskStatuses })
@@ -119,6 +118,5 @@ export function useTasksData({
return {
data: filteredData,
allStatuses,
- isLoading: isLoading || isStatusesLoading,
};
}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx
index 8831e44b173..2cb776f481a 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx
@@ -71,7 +71,6 @@ export function useTasksTable({
assigneeFilter,
}: UseTasksTableParams): {
table: Table
;
- isLoading: boolean;
slugColumnWidth: string;
rowSelection: RowSelectionState;
setRowSelection: (
@@ -87,7 +86,7 @@ export function useTasksTable({
const rowSelection = useRowSelectionStore((s) => s.rowSelection);
const setRowSelection = useRowSelectionStore((s) => s.setRowSelection);
- const { data: allData, isLoading } = useLiveQuery(
+ const { data: allData } = useLiveQuery(
(q) =>
q
.from({ tasks: collections.tasks })
@@ -348,5 +347,5 @@ export function useTasksTable({
autoResetExpanded: false,
});
- return { table, isLoading, slugColumnWidth, rowSelection, setRowSelection };
+ return { table, slugColumnWidth, rowSelection, setRowSelection };
}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
index aba18dad1a0..66ccea64bd1 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
@@ -24,6 +24,10 @@ import type {
} from "@superset/db/schema";
import type { AppRouter } from "@superset/trpc";
import { electricCollectionOptions } from "@tanstack/electric-db-collection";
+import {
+ createElectronSQLitePersistence,
+ persistedCollectionOptions,
+} from "@tanstack/electron-db-sqlite-persistence";
import type {
Collection,
LocalStorageCollectionUtils,
@@ -57,6 +61,10 @@ const columnMapper = snakeCamelMapper();
const electricUrl = `${env.NEXT_PUBLIC_ELECTRIC_URL}/v1/shape`;
+const persistence = createElectronSQLitePersistence({
+ invoke: (channel, request) => window.ipcRenderer.invoke(channel, request),
+});
+
const indexDefaults = {
autoIndex: "eager",
defaultIndexType: BasicIndex,
@@ -67,6 +75,21 @@ const createIndexedCollection = ((
) =>
createCollection({ ...config, ...indexDefaults })) as typeof createCollection;
+type ElectricSyncConfig = ReturnType;
+const createPersistedElectricCollection = ((config: ElectricSyncConfig) => {
+ const persisted = persistedCollectionOptions({
+ ...config,
+ persistence,
+ schemaVersion: 1,
+ // biome-ignore lint/suspicious/noExplicitAny: forces sync-wrapped overload
+ } as any);
+ return createCollection({
+ ...persisted,
+ ...indexDefaults,
+ // biome-ignore lint/suspicious/noExplicitAny: persisted utils widen generics
+ } as any);
+}) as unknown as typeof createCollection;
+
const apiKeyDisplaySchema = z.object({
id: z.string(),
name: z.string().nullable(),
@@ -176,7 +199,7 @@ const electricHeaders = {
},
};
-const organizationsCollection = createIndexedCollection(
+const organizationsCollection = createPersistedElectricCollection(
electricCollectionOptions({
id: "organizations",
shapeOptions: {
@@ -190,7 +213,7 @@ const organizationsCollection = createIndexedCollection(
);
function createOrgCollections(organizationId: string): OrgCollections {
- const tasks = createIndexedCollection(
+ const tasks = createPersistedElectricCollection(
electricCollectionOptions({
id: `tasks-${organizationId}`,
shapeOptions: {
@@ -224,7 +247,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const taskStatuses = createIndexedCollection(
+ const taskStatuses = createPersistedElectricCollection(
electricCollectionOptions({
id: `task_statuses-${organizationId}`,
shapeOptions: {
@@ -240,7 +263,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const projects = createIndexedCollection(
+ const projects = createPersistedElectricCollection(
electricCollectionOptions({
id: `projects-${organizationId}`,
shapeOptions: {
@@ -256,7 +279,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const v2Projects = createIndexedCollection(
+ const v2Projects = createPersistedElectricCollection(
electricCollectionOptions({
id: `v2_projects-${organizationId}`,
shapeOptions: {
@@ -288,7 +311,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const v2Hosts = createIndexedCollection(
+ const v2Hosts = createPersistedElectricCollection(
electricCollectionOptions({
id: `v2_hosts-${organizationId}`,
shapeOptions: {
@@ -306,7 +329,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const v2Clients = createIndexedCollection(
+ const v2Clients = createPersistedElectricCollection(
electricCollectionOptions({
id: `v2_clients-${organizationId}`,
shapeOptions: {
@@ -324,7 +347,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const v2UsersHosts = createIndexedCollection(
+ const v2UsersHosts = createPersistedElectricCollection(
electricCollectionOptions({
id: `v2_users_hosts-${organizationId}`,
shapeOptions: {
@@ -369,7 +392,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const v2Workspaces = createIndexedCollection(
+ const v2Workspaces = createPersistedElectricCollection(
electricCollectionOptions({
id: `v2_workspaces-${organizationId}`,
shapeOptions: {
@@ -396,7 +419,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const workspaces = createIndexedCollection(
+ const workspaces = createPersistedElectricCollection(
electricCollectionOptions({
id: `workspaces-${organizationId}`,
shapeOptions: {
@@ -412,7 +435,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const members = createIndexedCollection(
+ const members = createPersistedElectricCollection(
electricCollectionOptions({
id: `members-${organizationId}`,
shapeOptions: {
@@ -428,7 +451,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const users = createIndexedCollection(
+ const users = createPersistedElectricCollection(
electricCollectionOptions({
id: `users-${organizationId}`,
shapeOptions: {
@@ -444,7 +467,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const invitations = createIndexedCollection(
+ const invitations = createPersistedElectricCollection(
electricCollectionOptions({
id: `invitations-${organizationId}`,
shapeOptions: {
@@ -460,7 +483,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const agentCommands = createIndexedCollection(
+ const agentCommands = createPersistedElectricCollection(
electricCollectionOptions({
id: `agent_commands-${organizationId}`,
shapeOptions: {
@@ -484,7 +507,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const integrationConnections = createIndexedCollection(
+ const integrationConnections = createPersistedElectricCollection(
electricCollectionOptions({
id: `integration_connections-${organizationId}`,
shapeOptions: {
@@ -500,7 +523,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const subscriptions = createIndexedCollection(
+ const subscriptions = createPersistedElectricCollection(
electricCollectionOptions({
id: `subscriptions-${organizationId}`,
shapeOptions: {
@@ -516,7 +539,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const apiKeys = createIndexedCollection(
+ const apiKeys = createPersistedElectricCollection(
electricCollectionOptions({
id: `apikeys-${organizationId}`,
shapeOptions: {
@@ -532,7 +555,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const chatSessions = createIndexedCollection(
+ const chatSessions = createPersistedElectricCollection(
electricCollectionOptions({
id: `chat_sessions-${organizationId}`,
shapeOptions: {
@@ -558,7 +581,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const githubRepositories = createIndexedCollection(
+ const githubRepositories = createPersistedElectricCollection(
electricCollectionOptions({
id: `github_repositories-${organizationId}`,
shapeOptions: {
@@ -574,7 +597,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const githubPullRequests = createIndexedCollection(
+ const githubPullRequests = createPersistedElectricCollection(
electricCollectionOptions({
id: `github_pull_requests-${organizationId}`,
shapeOptions: {
@@ -590,7 +613,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const automations = createIndexedCollection(
+ const automations = createPersistedElectricCollection(
electricCollectionOptions({
id: `automations-${organizationId}`,
shapeOptions: {
@@ -606,7 +629,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);
- const automationRuns = createIndexedCollection(
+ const automationRuns = createPersistedElectricCollection(
electricCollectionOptions({
id: `automation_runs-${organizationId}`,
shapeOptions: {
diff --git a/bun.lock b/bun.lock
index f68abeb726f..a0de8ed3144 100644
--- a/bun.lock
+++ b/bun.lock
@@ -175,6 +175,8 @@
"@t3-oss/env-core": "^0.13.8",
"@tanstack/db": "0.6.5",
"@tanstack/electric-db-collection": "0.3.3",
+ "@tanstack/electron-db-sqlite-persistence": "0.1.9",
+ "@tanstack/node-db-sqlite-persistence": "0.1.9",
"@tanstack/react-db": "0.1.83",
"@tanstack/react-query": "^5.90.19",
"@tanstack/react-router": "^1.147.3",
@@ -2634,10 +2636,16 @@
"@tanstack/db-ivm": ["@tanstack/db-ivm@0.1.18", "", { "dependencies": { "fractional-indexing": "^3.2.0", "sorted-btree": "^1.8.1" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-+pZJiRKdoKRM5Epq9T7otD9ZJl82pRFauo7LKuJGrarjVKQ7r+QQlPe3kGdN9LEKSnuNGIWjX9OOY4M8kH4eLw=="],
+ "@tanstack/db-sqlite-persistence-core": ["@tanstack/db-sqlite-persistence-core@0.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@tanstack/db": "0.6.5" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-Ju4HcBzaSXTaPJFMRWNjiyXRfCmom0AXBr/22sqLoQWY1TjFM/UTbSSTQgsrExkNuFDVFiV8EDpCt8WUBFiKcQ=="],
+
"@tanstack/electric-db-collection": ["@tanstack/electric-db-collection@0.3.3", "", { "dependencies": { "@electric-sql/client": "^1.5.15", "@standard-schema/spec": "^1.1.0", "@tanstack/db": "0.6.5", "@tanstack/store": "^0.9.2", "debug": "^4.4.3" } }, "sha512-ywx5s6kv/ZNDszl2GwnlvZPlimEBAUREo9cEN9oCN7SyO36/5andaAKgjWQYXiXDc6FamjQfE6N6WuUFb0Zg0Q=="],
+ "@tanstack/electron-db-sqlite-persistence": ["@tanstack/electron-db-sqlite-persistence@0.1.9", "", { "dependencies": { "@tanstack/db-sqlite-persistence-core": "0.1.9" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-QlH4pt0vhw9BZIdf6iyIioFoUy58vJoe5gXNIpXFEXY1uP4awKpTJ7ZrVTlBPPpaYvbcJxMxqjusoFCRVA5XQg=="],
+
"@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="],
+ "@tanstack/node-db-sqlite-persistence": ["@tanstack/node-db-sqlite-persistence@0.1.9", "", { "dependencies": { "@tanstack/db-sqlite-persistence-core": "0.1.9", "better-sqlite3": "^12.6.2" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-irgaRzZ6eMV4Ky7fIoiy823vPfGllnsJPI0oqdjWS4kD09juktR8JfS0Gqg9ONBbwLB2kLBrC3ei72Dzz92RKg=="],
+
"@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.2.1", "", {}, "sha512-3PouiFjR4B6x1c969/Pl4ZIJleof1M0n6fNX8NRiC9Sqv1g06CVDlEaXUR4212ycGFyfq4q+t8Gi37Xy+z34iQ=="],
"@tanstack/query-core": ["@tanstack/query-core@5.95.2", "", {}, "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ=="],