Q195: a change reloads only what is not already shown, and the snapshot stops carrying records the window never reads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 09:20:45 -04:00
1 parent 2f79e4261f
commit 56eac39b1b
31 files changed
+146 -214

No files matched your search

+13 -6
View File
@@ -1,3 +1,4 @@
import { bumpDataVersion, currentDataVersion } from "../services/data-version";
import { createIpcGate } from "./ipc-gate";
import { app, dialog, shell, type BrowserWindow } from "electron";
import type { DatabaseConnection } from "../database/connection";
@@ -69,13 +70,18 @@ export function registerIpcHandlers(deps: IpcDeps): void {
const integrationKeys = new IntegrationKeyStore(deps.connection.db);
const providerOAuth = new ProviderOAuthService(integrationKeys);
const skills = new SkillService(deps.connection.db, deps.artifactRoot);
const notifyDataChanged = coalesce(() => {
const announceChange = coalesce(() => {
try {
if (!deps.mainWindow.isDestroyed() && !deps.mainWindow.webContents.isDestroyed()) {
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed", version: currentDataVersion() });
}
} catch { /* renderer may have been disposed during agent run */ }
}, 200);
// The version moves at once, so the snapshot an action returns already carries the change its own event announces.
const notifyDataChanged = () => {
bumpDataVersion();
announceChange();
};
const taskExecution = new TaskExecutionService(deps.connection.db, providers, skills, notifyDataChanged);
const social = new SocialService(deps.connection.db);
const files = new FileService(deps.connection.db, deps.artifactRoot);
@@ -456,11 +462,12 @@ export function registerIpcHandlers(deps: IpcDeps): void {
handle("disconnectComposioToolkit", async (input) => {
try {
await composioConnections.disconnectToolkit(input.orgId, input.toolkitSlug, input.label);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("composio_disconnect_failed", error instanceof Error ? error.message : String(error));
} finally {
// A disconnect that failed partway may still have written; the page reloads either way.
notifyDataChanged();
return fail("composio_disconnect_failed", error instanceof Error ? error.message : String(error));
}
});
handle("grantConnector", (input) => mutate(() => new ConnectorGrantsService(deps.connection.db).grantFromBoard(input)));
@@ -478,11 +485,11 @@ export function registerIpcHandlers(deps: IpcDeps): void {
handle("refreshComposioAccounts", async (input) => {
try {
await composioConnections.refreshAccounts(input.orgId);
notifyDataChanged();
return ok(snapshot.load());
} catch (error) {
return fail("composio_refresh_failed", error instanceof Error ? error.message : String(error));
} finally {
notifyDataChanged();
return fail("composio_refresh_failed", error instanceof Error ? error.message : String(error));
}
});
handle("windowMinimize", () => deps.mainWindow.minimize());
+2 -1
View File
@@ -1,3 +1,4 @@
import { bumpDataVersion } from "../services/data-version";
import type { BrowserWindow } from "electron";
import type { DatabaseSync } from "node:sqlite";
import { IPC_CHANNELS } from "../../shared/ipc";
@@ -22,7 +23,7 @@ export function startProviderBackgroundWorker(input: ProviderBackgroundWorkerInp
try {
const changedCount = await service.recoverBackgroundResponses();
if (changedCount > 0 && !input.mainWindow.isDestroyed()) {
input.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
input.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed", version: bumpDataVersion() });
}
} finally {
running = false;
+2 -1
View File
@@ -1,3 +1,4 @@
import { bumpDataVersion } from "../services/data-version";
import type { BrowserWindow } from "electron";
import type { DatabaseConnection } from "../database/connection";
import { IPC_CHANNELS } from "../../shared/ipc";
@@ -80,7 +81,7 @@ export function claimNextQueuedRun(connection: DatabaseConnection, excludedProvi
export function startTaskQueueWorker(input: TaskQueueWorkerInput): () => void {
const notifyDataChanged = () => {
if (!input.mainWindow.isDestroyed()) {
input.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" });
input.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed", version: bumpDataVersion() });
}
};
const taskExecution = input.taskExecution ?? new TaskExecutionService(input.connection.db, undefined, undefined, notifyDataChanged);
+11
View File
@@ -0,0 +1,11 @@
let version = 0;
/** Marks a change the window should show; snapshots and change events carry the count so the window never reloads what it already has. */
export function bumpDataVersion(): number {
version += 1;
return version;
}
export function currentDataVersion(): number {
return version;
}
+2 -31
View File
@@ -1,3 +1,4 @@
import { currentDataVersion } from "./data-version";
import { loadViewState } from "./view-state";
import { loadOnboardingDraft } from "./onboarding-progress";
import type { DatabaseSync, StatementSync, SQLInputValue } from "node:sqlite";
@@ -71,32 +72,17 @@ export class SnapshotService {
.map((provider) => ({ ...provider, enabled: toBool(provider.enabled), backgroundEnabled: toBool(provider.backgroundEnabled) }));
const providerAuthProfiles = list<AppSnapshot["providerAuthProfiles"][number]>(this.db, "provider_auth_profiles", "created_at desc")
.map((profile) => ({ ...profile, active: toBool(profile.active) }));
const providerAuthSessions = list<AppSnapshot["providerAuthSessions"][number]>(this.db, "provider_auth_sessions", "created_at desc");
const providerRateLimitObservations = list<AppSnapshot["providerRateLimitObservations"][number]>(
this.db,
"provider_rate_limit_observations",
"observed_at desc",
);
const providerUsageWindows = list<ProviderUsageWindowRow>(this.db, "provider_usage_windows", "window_start desc");
const providerRateLimits = buildProviderRateLimits(providers, providerUsageWindows);
const files = list<AppSnapshot["files"][number]>(this.db, "file_objects", "created_at desc")
.map((file) => ({ ...file, pinned: toBool(file.pinned) }));
const legalHolds = list<AppSnapshot["legalHolds"][number]>(this.db, "legal_holds", "created_at desc")
.map((hold) => ({ ...hold, active: toBool(hold.active) }));
const retentionFreezes = list<AppSnapshot["retentionFreezes"][number]>(this.db, "retention_freezes", "created_at desc")
.map((freeze) => ({ ...freeze, active: toBool(freeze.active) }));
const employeeIdentities = list<AppSnapshot["employeeIdentities"][number]>(this.db, "employee_identities", "created_at desc")
.map((identity) => ({ ...identity, active: toBool(identity.active) }));
const featureFlags = list<AppSnapshot["featureFlags"][number]>(this.db, "feature_flags", "key asc")
.map((flag) => ({ ...flag, enabled: toBool(flag.enabled) }));
const operationalEvents = list<AppSnapshot["operationalEvents"][number]>(this.db, "operational_events", "created_at desc")
.map((event) => ({ ...event, success: toBool(event.success) }));
return {
activeOrgId,
uiPreferences: this.resolveUiPreferences(),
onboardingDraft: loadOnboardingDraft(this.db),
viewState: loadViewState(this.db),
dataVersion: currentDataVersion(),
autonomousPaused: this.resolveAutonomousPaused(),
integrationKeys: new IntegrationKeyStore(this.db).statuses(),
organizations,
@@ -127,7 +113,6 @@ export class SnapshotService {
chatAttachments: forMessages(this.db, "chat_attachments", "message_id desc", loadedMessageIds),
chatMentions: forMessages(this.db, "chat_mentions", "message_id desc, offset_start asc", loadedMessageIds),
chatSlashInvocations: forMessages(this.db, "chat_slash_invocations", "created_at desc", loadedMessageIds),
boardInterventions: list(this.db, "board_interventions", "created_at asc"),
chatPresence: list(this.db, "chat_presence", "last_seen_at desc"),
chatReadStates: list(this.db, "chat_read_state", "last_read_at desc"),
files,
@@ -136,7 +121,6 @@ export class SnapshotService {
scheduledPosts: list(this.db, "scheduled_posts", "scheduled_for asc"),
providers,
providerAuthProfiles,
providerAuthSessions,
providerCatalog: PROVIDER_CATALOG,
// Explicit projection: raw_response_json alone weighs megabytes across
// 100 rows and no renderer surface reads it.
@@ -150,7 +134,6 @@ export class SnapshotService {
`), activeOrgId ?? "").map((row) => camel(row) as unknown as AppSnapshot["providerRequests"][number]),
providerRateLimits,
usageStats: buildUsageStats(this.db, new Date()),
providerRateLimitObservations,
skillDefinitions: list(this.db, "skill_definitions", "created_at asc"),
socialAccounts: list(this.db, "social_accounts", "created_at asc"),
socialAccountGrants: list(this.db, "social_account_grants", "created_at asc"),
@@ -164,19 +147,7 @@ export class SnapshotService {
policyRules: list(this.db, "policy_rules", "created_at asc"),
migrationReports: list(this.db, "migration_reports", "created_at desc"),
complianceExports: list(this.db, "compliance_exports", "created_at desc"),
legalHolds,
retentionFreezes,
employeeIdentities,
serviceIdentities: list(this.db, "service_identities", "created_at desc"),
roleGrants: list(this.db, "role_grants", "created_at desc"),
accessDecisions: recentInOrg<AppSnapshot["accessDecisions"][number]>(this.db, "access_decisions", "created_at desc", 200, activeOrgId),
orgDepartments: list(this.db, "org_departments", "name asc"),
hiringPolicies: list(this.db, "hiring_policies", "department_name asc"),
approvalRoutingRules: list(this.db, "approval_routing_rules", "created_at asc"),
approvalAssignments: list(this.db, "approval_assignments", "created_at desc"),
featureFlags,
sloTargets: list(this.db, "slo_targets", "signal_key asc"),
operationalEvents,
auditEvents: recentInOrg<AppSnapshot["auditEvents"][number]>(this.db, "audit_events", "created_at desc", 200, activeOrgId),
documents: list(this.db, "documents", "updated_at desc"),
knowledgeEntries: list(this.db, "knowledge_entries", "updated_at desc"),