DEV Community

Ekong Ikpe
Ekong Ikpe

Posted on

I think I just made PWAs obsolete. Or maybe I upgraded them. I genuinely can't tell. 🤔

1 signal

I watched a movie yesterday (Signal One, 2026) — just when I needed a sci-fi break.

Okay 🤦 I'm obsessed with the browser but don't think all I do is Gnoke. 😉


Why This Post Matters

PWAs have a Service Worker. A manifest. Maybe some IndexedDB if you're disciplined.

And when Android kills your tab — because Android kills everything, eventually — you come back to a blank page. Or the home screen. Or whatever the browser decides to show you.

The app is still "installed." The data is still there. But the session is gone. Where you were. What you were doing. That context that made the app feel like yours.

We just accept this. We've always accepted this.

I don't accept it in Gnoke Station 2 — my browser OS where tabs are apps. Shopping list. Council. Notes. They're supposed to feel real.

Real apps don't forget you.


What PWAs actually give you (and what they don't)

Service Worker caches your assets. Cold boot works offline. Good.

Manifest tells the OS your name and icon. Install prompt appears. Good.

Neither of them knows where you were.

That's the gap. PWAs solved the delivery problem. Nobody solved the session problem.

Every PWA on your phone has the same quiet flaw: open it after Android clears it from memory, and you start over.


Gnoke-Spirit as a solution

Before the tab dies — on pagehide — snapshot everything. Where the user was. What they typed. The URL hash. Scroll position. Every form field that isn't a password.

Write it to IndexedDB. Not localStorage — IndexedDB survives process kills.

On the next boot, before the app renders anything, read it back. Restore it. Fire an event.

The app wakes up exactly where the user left it. Not approximately. Exactly.

I called it resurrection.

It's best-effort — pagehide as the primary trigger, visibilitychange as backup, plus debounced input saves. Abrupt kills may lose the last few seconds. Still vastly better than starting from scratch.


The part I didn't expect

Once spirit was working I looked at the other pieces sitting around it.

Service Worker. Already running. Knows whether the page was served from cache or the network.

Manifest. Already declared. Handles the OS install layer.

Spirit. Knows the full session state.

Three things. Three completely separate browser concerns. Not one of them talks to the others.

But I had boot.js — a single file every Gnoke app loads first. I thought: what if boot.js just asked all three before firing?

{
  source: 'cache',        // SW told boot.js this
  resurrecting: true,     // spirit found a snapshot
  snapshot: {
    hash: '#list:abc123',
    scroll: { x: 0, y: 340 },
    forms: [...]
  }
}
Enter fullscreen mode Exit fullscreen mode

One event. One decision point. Full context. No manual wiring.

Instead of this — per app, every time:

window.addEventListener('load', () => {
  const saved = localStorage.getItem('last-page');
  if (saved) navigateTo(saved);
});

navigator.serviceWorker.ready.then(reg => {
  const fromCache = reg.active?.state === 'activated';
  if (fromCache) skipLoadingScreen();
});
Enter fullscreen mode Exit fullscreen mode

You get this:

document.addEventListener('gnoke:boot', ({ detail }) => {
  if (detail.resurrecting) navigateTo(detail.snapshot.hash);
  if (detail.source === 'cache') skipLoadingScreen();
});
Enter fullscreen mode Exit fullscreen mode

The apps get smarter for free. The coordinator does the work once, centrally.


Against native apps

Android kills your native app. Android kills your browser tab. Same question either way: how do we get the user back to where they were?

Native platforms have lifecycle APIs, Bundles, ViewModels, Room, CoreData — a decade of patterns built around surviving process death.

The web has mostly been pretending the problem doesn't exist.

Spirit isn't doing something native apps can't. It's bringing the same lifecycle resilience to the browser with a tiny amount of vanilla JavaScript.

Spirit treats browser state as the source of truth — the URL, the hash, scroll position, focused field, form values. Those things already exist. Spirit snapshots them. The restore isn't a replay. It's a read.

Spirit doesn't know what a shopping list is. It doesn't need to. If the list encodes its state as #list:abc123 — Spirit captures it automatically.

The hash is the state. Spirit snapshots it. Boot restores it.


The diagram

[ Service Worker ] ---> (Network Layer)  \
[ Web Manifest   ] ---> (OS Layer)        +---> [ boot.js ] ---> Event: `gnoke:boot`
[ Gnoke Spirit   ] ---> (Session Layer)  /
Enter fullscreen mode Exit fullscreen mode

Service Worker owns the network layer.
Manifest owns the OS layer.
Spirit owns the session layer.

Boot.js sits at the intersection. Making it conscious of all three costs almost nothing.


What I'm actually claiming

The delivery problem was solved by Service Workers.
The install problem was solved by manifests.
What was missing was a lifecycle.

Not storage. Not caching. Not installation. Continuity.

Service Worker gave PWAs a body.
The manifest gave them an identity.
Spirit gives them a memory.

That feels less like a webpage. And a lot more like an application.


The coordinator pattern is part of Gnoke Station — open source, MIT.

If you're a senior dev who works close to the browser — primitives, lifecycle, platform APIs — I'd genuinely welcome your eyes on the architecture. 🤷


References

— Edmund Sparrow, Gnoke Suite

Top comments (0)