Skip to content

Commit 0765166

Browse files
matt2eclaude
andcommitted
feat(staged): add resilient SWR cache, PWA support, and instant iOS web resume
Add a stale-while-revalidate cache layer with cache invalidation and page-lifecycle listeners, register a service worker for PWA support, and make iOS web resume near-instant instead of a staged reveal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
1 parent 8f948a3 commit 0765166

27 files changed

Lines changed: 2353 additions & 183 deletions

apps/staged/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
77
<link rel="manifest" href="/manifest.webmanifest" />
88
<meta name="theme-color" content="#060606" />
9+
<meta name="apple-mobile-web-app-capable" content="yes" />
10+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
911
<meta
1012
name="viewport"
1113
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"

apps/staged/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@types/node": "^24.10.1",
2929
"bits-ui": "^2.18.1",
3030
"clsx": "^2.1.1",
31+
"fake-indexeddb": "^6.2.5",
3132
"prettier": "^3.7.4",
3233
"prettier-plugin-svelte": "^3.4.1",
3334
"shadcn-svelte": "^1.2.7",
@@ -52,6 +53,7 @@
5253
"@tauri-apps/plugin-store": "^2.4.2",
5354
"@tauri-apps/plugin-updater": "^2.10.0",
5455
"ansi-to-html": "^0.7.2",
56+
"idb-keyval": "^6.2.2",
5557
"marked": "^17.0.1",
5658
"sanitize-html": "^2.17.0",
5759
"shiki": "^3.20.0"

apps/staged/src/App.svelte

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import { projectStateStore } from './lib/stores/projectState.svelte';
4444
import { initBloxEnv } from './lib/stores/bloxEnv.svelte';
4545
import { listenForSessionStatus } from './lib/listeners/sessionStatusListener';
46+
import { listenForCacheInvalidation } from './lib/listeners/cacheInvalidationListener';
47+
import { listenForPageLifecycle } from './lib/listeners/pageLifecycleListener';
4648
import { darkMode } from './lib/stores/isDark.svelte';
4749
import * as prPollingService from './lib/services/prPollingService';
4850
import { projectsList } from './lib/features/projects/projectsSidebarState.svelte';
@@ -61,6 +63,8 @@
6163
let unlistenZoomOut: UnlistenFn | undefined;
6264
let unlistenZoomReset: UnlistenFn | undefined;
6365
let unlistenSessionStatus: UnlistenFn | undefined;
66+
let unlistenCacheInvalidation: UnlistenFn | undefined;
67+
let unlistenPageLifecycle: (() => void) | undefined;
6468
let unregisterShortcuts: (() => void) | null = null;
6569
let stopUpdaterLoop: (() => void) | null = null;
6670
let storeIncompat = $state<StoreIncompatibility | null>(null);
@@ -86,7 +90,7 @@
8690
const projectId = navigation.selectedProjectId;
8791
if (!projectId) return;
8892
try {
89-
const branches = await commands.listBranchesForProject(projectId);
93+
const { data: branches } = await commands.listBranchesForProject(projectId);
9094
await Promise.allSettled(branches.map((b) => commands.refreshBranchGitState(b.id)));
9195
} catch {
9296
// best-effort refresh; ignore failures
@@ -228,6 +232,14 @@
228232
darkMode.init();
229233
document.addEventListener('keydown', handleKonamiKey);
230234
235+
// Web resume accelerator: the restored project id is available synchronously
236+
// (navigation seeds it from localStorage at module load). Warm that project's
237+
// branch timelines in parallel right away so cards find data ready, rather
238+
// than each doing its own serialized IndexedDB read after ProjectHome mounts.
239+
if (navigation.selectedProjectId) {
240+
void commands.warmProjectTimelines(navigation.selectedProjectId);
241+
}
242+
231243
// Listen for the app menu Preferences item.
232244
unlistenSettings = listenToEvent('menu:settings', () => {
233245
if (!triggerShortcut('app-open-settings')) openSettings();
@@ -254,6 +266,8 @@
254266
// Global session-status listener — must live at App level so it works
255267
// regardless of which view the user is on. See sessionStatusListener.ts.
256268
unlistenSessionStatus = listenForSessionStatus();
269+
unlistenCacheInvalidation = listenForCacheInvalidation();
270+
unlistenPageLifecycle = listenForPageLifecycle();
257271
258272
try {
259273
await initPreferences();
@@ -422,6 +436,8 @@
422436
unlistenZoomOut?.();
423437
unlistenZoomReset?.();
424438
unlistenSessionStatus?.();
439+
unlistenCacheInvalidation?.();
440+
unlistenPageLifecycle?.();
425441
stopUpdaterLoop?.();
426442
});
427443

0 commit comments

Comments
 (0)