Q184: the page, its sections, filters and selections survive page switches and restarts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 07:46:21 -04:00
1 parent 2790d88186
commit 72cf57ce3a
27 files changed
+235 -38

No files matched your search

+1
View File
@@ -15,6 +15,7 @@ export const IPC_CONTRACTS: { [K in InvokedMethod]: z.ZodType<InputOf<K>> } = {
loadSnapshot: noInput,
relaunchApp: noInput,
reportUnsaved: z.strictObject({ unsaved: z.boolean() }),
saveViewState: z.strictObject({ key: z.string().min(1).max(200), value: z.json().refine((value) => JSON.stringify(value).length <= 4_000, "View state value is too large.") }),
runningSessionCount: noInput,
pickDirectory: noInput,
listConnectorCatalog: noInput,
+9
View File
@@ -43,6 +43,7 @@ import { deleteWorkspace, setPrimaryWorkspace, updateWorkspace } from "../servic
import { setProviderModel } from "../services/provider-default-model";
import { completeSetup, saveOnboardingDraft } from "../services/onboarding-progress";
import { setUnsavedInput } from "./unsaved-guard";
import { saveViewState } from "../services/view-state";
import { providerDisconnectImpact } from "../services/provider-disconnect-impact";
export interface IpcDeps {
@@ -152,6 +153,14 @@ export function registerIpcHandlers(deps: IpcDeps): void {
}
});
handle("completeSetup", (input) => mutate(() => completeSetup(deps.connection.db, input.orgId)));
handle("saveViewState", (input) => {
try {
saveViewState(deps.connection.db, input.key, input.value);
return ok(null);
} catch (error) {
return fail("view_state_failed", error instanceof Error ? error.message : String(error));
}
});
handle("reportUnsaved", (input) => {
setUnsavedInput(input.unsaved);
return ok(null);
@@ -0,0 +1,7 @@
// Where the operator was (page, section, filters, selected records), so switching away or restarting returns there.
export const viewStateSchema = `
alter table app_state add column view_state_json text;
insert into schema_migrations(version, name, applied_at)
values (106, 'view_state', datetime('now'));
`;
+2
View File
@@ -104,6 +104,7 @@ import { goalDueDateSchema } from "./migrations/102-goal-due-date";
import { workVerdictsSchema } from "./migrations/103-work-verdicts";
import { workLanguageSchema } from "./migrations/104-work-language";
import { onboardingProgressSchema } from "./migrations/105-onboarding-progress";
import { viewStateSchema } from "./migrations/106-view-state";
interface Migration {
version: number;
@@ -217,6 +218,7 @@ const MIGRATIONS: Migration[] = [
{ version: 103, name: "work_verdicts", sql: workVerdictsSchema },
{ version: 104, name: "work_language", sql: workLanguageSchema },
{ version: 105, name: "onboarding_progress", sql: onboardingProgressSchema },
{ version: 106, name: "view_state", sql: viewStateSchema },
];
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
+2
View File
@@ -1,3 +1,4 @@
import { loadViewState } from "./view-state";
import { loadOnboardingDraft } from "./onboarding-progress";
import type { DatabaseSync, StatementSync } from "node:sqlite";
import type { AppSnapshot } from "../../shared/domain";
@@ -88,6 +89,7 @@ export class SnapshotService {
activeOrgId,
uiPreferences: this.resolveUiPreferences(),
onboardingDraft: loadOnboardingDraft(this.db),
viewState: loadViewState(this.db),
autonomousPaused: this.resolveAutonomousPaused(),
integrationKeys: new IntegrationKeyStore(this.db).statuses(),
organizations,
+20
View File
@@ -0,0 +1,20 @@
import type { DatabaseSync } from "node:sqlite";
export function loadViewState(db: DatabaseSync): Record<string, unknown> {
const row = db.prepare("select view_state_json from app_state where id = 1").get() as { view_state_json: string | null } | undefined;
return row?.view_state_json ? JSON.parse(row.view_state_json) as Record<string, unknown> : {};
}
/** Remembers one piece of where the operator was; null forgets it. */
export function saveViewState(db: DatabaseSync, key: string, value: unknown): void {
const path = `$.${JSON.stringify(key)}`;
db.prepare(`
insert into app_state(id, view_state_json, updated_at) values (1, json_object(), ?)
on conflict(id) do nothing
`).run(new Date().toISOString());
if (value === null) {
db.prepare("update app_state set view_state_json = json_remove(coalesce(view_state_json, json_object()), ?) where id = 1").run(path);
return;
}
db.prepare("update app_state set view_state_json = json_set(coalesce(view_state_json, json_object()), ?, json(?)) where id = 1").run(path, JSON.stringify(value));
}