The tab left open overnight

D4U7 had to reconcile sleep, reconnect, a new application version, expired authority, and edits made elsewhere before claiming an old page was current.

The most destructive D4U7 test did not involve packet loss, a crashed database, or two people typing at once. I closed a laptop on Friday and opened it on Monday.

The tab looked exactly as I had left it. That was the problem.

While the laptop slept, I edited the same synthetic proposal from another device. The study room moved into review and then recorded a decision. My original session expired. A new application version deployed with a changed local-storage schema. One draft operation remained in the old tab's IndexedDB outbox.

On Monday, the page showed Friday's room as if it were current. The network indicator turned green. The editor still allowed submission. Nothing explained that the proposal had a newer revision or that the room no longer accepted ordinary edits.

The stale client was not a broken cache. It was a replica with old code, old data, pending intent, and expired authority. Reconnecting the socket solved only one of those facts.

The original offline test was too theatrical

My first offline demonstration followed a familiar sequence:

  1. Load a room while online.
  2. Disable the network.
  3. Write a response.
  4. Restore the network.
  5. Watch the response appear on the server.

The demo proved that the application shell loaded from a service-worker cache, a draft survived in IndexedDB, and one queued operation could be delivered. It took less than a minute. Almost nothing else had time to change.

The base document revision remained current. Authentication remained valid. The application and service-worker versions matched. The room lifecycle did not move. The operation was still permitted. No other device edited the same contribution.

I had tested transport interruption and called it offline collaboration.

The overnight scenario introduced time. Time allowed every independent state to diverge. That made it a better release test than toggling the browser's network checkbox.

I kept the quick demo as a focused check. It stopped representing the product promise.

Resume became an explicit product transition

The browser exposes visibility and connectivity hints, but none says “the world is now safe to use.” A page can become visible without a network. A network can return while credentials remain expired. A socket can connect before missed document revisions are reconciled.

D4U7 entered a resuming state when a page returned after a meaningful pause, connectivity returned, or the application detected uncertain continuity.

The resume sequence was ordered:

  1. Inspect application, service-worker, and local-database compatibility.
  2. Preserve and inventory local drafts and outbox operations.
  3. Re-establish or request authentication without changing operation identity.
  4. Fetch the current room lifecycle and revision.
  5. Reconcile local bases and delivery policies against current state.
  6. Fetch a delta or snapshot until the client has continuous room history.
  7. Resume live delivery from the accepted revision.
  8. Enable only actions valid under the resulting state.

Existing content stayed readable. The page did not flash into an empty loading state merely because freshness was uncertain. It displayed the last confirmed revision and a concise resuming status. Actions requiring current authority waited.

If the network remained unavailable, the client could still support reading and drafting under its offline policy. It did not label the room current.

Connection and currency were separate

The early status model had online and offline. Monday's tab became online immediately and remained stale.

I separated four dimensions:

connection: offline | connecting | online
room: unknown | current | behind | reconciling
draft: clean | local | saving | conflict
outbox: empty | pending | blocked | attention

An online client could be behind. A current room could contain a local draft not yet delivered. An outbox could be blocked on authentication without making the draft invalid. A conflict could exist while the connection was healthy.

The interface combined these states into ordinary language instead of showing four technical badges:

  • You're offline. Draft saved on this device.
  • Checking what changed since Friday.
  • This room is current; one contribution is waiting to send.
  • Your draft is safe, but the proposal changed elsewhere. Review both versions.
  • The room was decided while this tab was away. Your draft will not be posted automatically.

The more precise internal model made the external explanation simpler. One vague green icon had required the person to discover every exception by attempting an action.

Every durable room transition advanced a monotonically increasing room revision. Contribution revisions had their own immutable identities, and the room revision placed their effects in the broader state.

The client stored the last continuous room revision it had accepted. On resume it sent that cursor. If the server retained every missing event and the client's schema could read them, it returned a delta. Otherwise it returned a snapshot plus the immutable documents needed to explain current state.

The client did not set current merely because a response arrived. It verified that the delta began after its cursor, contained no gaps, and ended at the server's declared revision. A live stream then started from that accepted point.

If a stream event skipped a revision, the room became behind and requested repair. Existing content remained visible with its stale marker. The client never appended revision 42 after 40 and hoped 41 did not matter.

This was T04P's continuity handshake applied to authored work. The difference was consequence. A missing dashboard sample could affect freshness. A missing room event could hide a decision, permission change, or contribution revision.

Continuous history was part of the product state, not transport plumbing.

Pending intent needed a stable identity

Friday's draft operation had a generated operation ID, the contribution ID it intended to change, and the base contribution and room revisions on which it had been authored.

type PendingOperation = {
  operationId: string
  roomId: string
  kind: 'revise-proposal'
  contributionId: string
  baseContributionRevision: string
  baseRoomRevision: number
  body: string
  createdAt: string
  deliveryPolicy: 'revalidate'
}

The operation ID remained stable through timeouts, authentication renewal, and retries. The server retained a receipt so the client could ask whether an unknown attempt had already succeeded.

The base revisions were equally important. The draft did not mean “replace whatever text is current with this body.” It meant “starting from this known revision, I intended this change.”

On Monday, the current contribution revision differed and the room lifecycle had advanced. The server could not accept the operation as an ordinary revision. The client preserved it as attention-required work.

Stable identity made delivery safe to repeat. Base identity made the intent safe to interpret.

Authentication failure was not contribution failure

The first implementation attempted the operation, received an unauthorized response, and marked it failed. After signing in, pressing Save created a new operation ID.

That sequence confused three different states:

  • The server did not authenticate the request.
  • The authenticated person might no longer have permission for the action.
  • The business operation itself might have been accepted before a response was lost.

A missing or expired session blocked delivery. It did not invalidate the local text. D4U7 kept the same operation pending while asking the person to authenticate. After successful authentication, it checked the operation receipt before retrying.

If the current account lacked room membership or the room lifecycle prohibited the edit, the operation became rejected with a recoverable local draft. The interface offered copy and export before any destructive action.

The application never used “clear site data and try again” as a recovery procedure. Browser storage contained the only copy of work the server had never received.

Authority was evaluated fresh; intent was preserved independently.

A closed room changed the delivery policy

Not every offline action deserved automatic delivery when connectivity returned.

Adding a standalone piece of evidence to an open room might remain valid if its base context had not changed. A reaction with stable set semantics could often merge. Recording a final decision, accepting an action, or revising a proposal after the review window changed required fresh state.

Friday's operation had revalidate policy. On Monday, the room was decided. D4U7 did not publish the draft as a new comment merely to avoid data loss, and it did not discard it because the original action was no longer legal.

The recovery page explained:

You wrote this revision before the room was decided. It has not been posted. Compare it with proposal revision 6, copy it, or ask to reopen the room with a reason.

This was a more conservative promise than “works offline.” The product preserved work and delivered only what remained meaningful under current state.

The June outbox design would classify these policies more deeply. The overnight failure established why a universal queue was insufficient.

During resume, the server returned proposal revision 6. The local editor had revision 4 as its base and unsent text derived from it.

My first reconciliation code updated the room cache, and the editor subscribed to that cache. The new server body replaced the local text before the conflict UI appeared. The system correctly detected a conflict after destroying one side of it.

I separated cached server state from local intent. The server projection could advance to revision 6 while the editor retained its draft and base revision 4. Conflict state linked all three:

  • Shared base: revision 4.
  • Current server: revision 6.
  • Local draft: unsent body derived from revision 4.

The conflict page offered a three-way merge proposal for Markdown, showed changed regions, and preserved both immutable inputs. The author could accept the merge, choose either version, or copy the draft elsewhere.

Because the room had closed, accepting a textual merge did not automatically publish it. Conflict resolution and permission to create a new revision were separate decisions.

Data freshness should never be implemented by erasing the work that made the client different.

A new service worker meant two applications coexisted

While the tab slept, a new service worker installed. The old page still ran JavaScript from the previous release. Activating the new worker immediately could cause it to serve an asset graph or local-data behavior the page did not expect.

Waiting forever had another cost. Long-lived tabs could remain on vulnerable or incompatible code for days.

I versioned three contracts separately:

  • Static application shell and asset manifest.
  • Client-to-server protocol range.
  • IndexedDB schema for drafts, snapshots, and outbox operations.

A compatible worker could wait and take control on the next navigation without disrupting the editor. An incompatible update prompted a deliberate transition after local intent had been migrated or exported.

The update message did not say only “A new version is available.” It said whether the current draft was safe, whether pending operations could migrate, and why refresh was required.

The old page and new worker negotiated through a small version message. If they could not, the worker avoided writing the local database and the page remained responsible for preserving its own data until reload.

The August service-worker article would narrow this boundary further. The overnight scenario proved that “activate latest immediately” was not a harmless default.

Local database migrations began with irreplaceable records

The new D4U7 release changed the outbox shape. It replaced a boolean autoSend with an explicit delivery-policy enum and added the base room revision.

A migration over cached room projections could fail safely because the client could fetch them again. A migration over unsent drafts and operations could destroy the only copy.

I divided local records into:

  • Irreplaceable intent: drafts, pending operations, conflict choices, and receipt queries whose server outcome was unknown.
  • Rebuildable projections: room snapshots, search indexes, and derived digests.
  • Versioned assets: owned by the service-worker cache lifecycle.

Migration protected intent first. It copied records into the new shape, validated counts and identifiers, and retained the old store until the new application confirmed it could read and present every item. If automatic migration could not interpret an operation, the update flow offered an export before continuing.

Captured IndexedDB fixtures from earlier releases became test inputs. A device returning after several versions had to take the same supported path, not only upgrade from yesterday's schema.

The cache was disposable. The outbox was user work.

“Last synchronized” was more useful than “online”

D4U7 displayed when a room revision had last been confirmed and whether newer state was known to exist. The label was not a promise that nothing had changed one millisecond later. It bounded the client's claim.

On resume, the page said Last confirmed Friday at room revision 31. After reconciliation it said Current through revision 38. If the stream disconnected after that, it could say Updates paused; showing revision 38.

I avoided a constantly ticking “stale for 13 seconds” label that would make ordinary reading feel broken. The interface increased prominence when staleness affected an attempted action or crossed a meaningful interval.

Cached content remained useful. A person could read the prior decision context while offline. The age and revision helped them decide how much authority to give it.

This repeated a principle from T04P and W93H: stale information is not useless information. Unmarked stale information is the problem.

Freshness became visible without making connectivity the center of every screen.

Returning attention also needed synchronization

After the protocol reconciled revisions, the room was technically current. I still did not know what had changed in a useful way.

D4U7 generated a digest from revision 31 to 38:

  • Proposal B was revised twice; its date and recording constraint changed.
  • The question blocking Proposal B was answered with new evidence.
  • Proposal A was withdrawn.
  • A decision selected Proposal B revision 6 and recorded one dissent.
  • One follow-up action remains unaccepted.

The digest grouped domain transitions rather than listing every message and edit. Each item linked to the immutable revision and full chronology.

The client tracked the last room revision I explicitly acknowledged, not merely the last one downloaded. A background tab could synchronize bytes without synchronizing attention.

This produced two cursors: delivered state and understood-through state. The second was voluntary and used only for consequential handoffs. It did not become a hidden reading-speed metric.

The network could close a revision gap automatically. The product needed to help a person close the corresponding mental gap.

Actions re-enabled according to fresh state

The early interface disabled the whole page during resume, then enabled everything when the fetch succeeded. That was easy to implement and too coarse.

Reading cached content did not require current authority. Editing a local draft could remain safe even while offline. Publishing a revision required current room state and membership. Recording a decision required the room to be in the proper phase and the selected proposal revision to remain current.

Each command declared prerequisites:

revise proposal
  requires: authenticated member, room open, current contribution base
 
save local draft
  requires: compatible local database
 
record decision
  requires: authenticated decision authority, review phase,
            current room revision, selected immutable proposal revision

The interface used those prerequisites to explain why an action was available, waiting, or no longer valid. A gray button with no reason would have turned the state model into another puzzle.

This also made offline support honest. The product could say exactly which actions remained meaningful without a network instead of promising an offline version of every workflow.

Resume itself could be interrupted. The network might disappear during snapshot fetch. Authentication could complete in another tab. A service-worker update could become ready while conflict reconciliation was open. The person could navigate to another room before the old one finished.

Every resume attempt received an identity. Results applied only if they matched the current room and attempt. A later attempt superseded earlier network work, but stable outbox operations kept their own identity and were not recreated.

The state machine retained progress safely:

  • Compatibility inspection could complete offline.
  • Local intent inventory was a local transaction.
  • Authentication could remain blocked without losing work.
  • A partial delta never advanced the continuous room cursor.
  • Conflict UI retained immutable base and both revisions across reload.

I tested resume by pausing at every boundary and reloading. The product should not require the recovery path itself to complete atomically in one foreground session.

That requirement made the implementation more deliberate and the failure messages more specific.

Browser events were hints, not truth

online, offline, visibilitychange, and service-worker lifecycle events helped start checks. None proved the corresponding domain state.

The browser could report online while the server or captive network remained unreachable. A page could become visible for a preview without the person engaging. A service worker could activate while the API protocol was incompatible. A socket opening did not prove revision continuity.

I used browser events to schedule verification, not to set room truth directly. The server revision, protocol handshake, runtime version, authentication result, and operation receipts supplied stronger evidence.

To avoid a request stampede when a laptop woke, the client coalesced resume triggers. One active attempt could absorb visibility, network, and focus events. Backoff with jitter handled repeated connection failure, while an explicit Retry action let the person request another attempt.

The interface showed the result of verification rather than narrating every browser event.

This kept platform signals in their proper role: useful observations with bounded meaning.

The overnight scenario became a fixture family

One manual Friday-to-Monday test was revealing and too slow for ordinary development. I encoded its states into controllable scenarios:

  • Same app version, newer room revision, no local edits.
  • Local draft with unchanged base.
  • Local draft with conflicting contribution revision.
  • Pending operation with lost acknowledgement.
  • Expired session with still-valid membership.
  • Revoked membership with recoverable local work.
  • Closed room with a previously valid pending action.
  • Compatible app update.
  • Incompatible local schema requiring migration.
  • Missing revision in the reconnect delta.

A controllable clock and event server delivered changes in defined orders. Browser tests used captured local-database fixtures and simulated sleep through page lifecycle transitions. Manual tests still covered real browser throttling and service-worker replacement.

Each scenario asserted both data safety and explanation. It was not enough that the draft remained in IndexedDB; the person had to be able to find, copy, reconcile, or deliberately discard it.

The family became more valuable than the original offline toggle because it tested reasons for divergence, not only lack of transport.

The Web encourages a temporary request-response picture: load a page, perform an action, receive a result. A tab left open overnight behaves more like a replica.

It has a code version, a data snapshot, local operations, cached assets, credentials, clocks, and an uncertain relationship to authority. The server may move independently. Another device may write. The client may disappear without closing its leases or learning the outcome of its requests.

Calling it a replica did not require adopting a general distributed database algorithm. It changed the questions I asked:

  • What version does this client know?
  • Which local intent is durable?
  • Which operations are safe to replay?
  • How is continuity proved after reconnect?
  • What changed while the client was absent?
  • Which authority must be checked again?
  • Can old and new code coexist without corrupting local state?

Those questions produced concrete product states and recovery paths.

Monday's page earned currency back in stages

In the revised scenario, opening the laptop no longer produced a confident stale editor.

The page showed Friday's room as last confirmed revision 31 and entered resume. It inventoried the safe local draft, detected the new app version, renewed authentication, fetched revision 38, and found that the room was decided. The new contribution revision created a conflict with the local draft. The application migrated the outbox record, retained both texts, and explained why automatic delivery no longer applied.

The room digest brought attention from revision 31 to 38. The decision record remained intact. The local draft could be copied or used as the reason for a deliberate amendment. The live stream began only after snapshot continuity was established.

Nothing about this path was as visually impressive as an instant offline submission. It was more respectful of the work.

Sleep, deployment, authentication, concurrent editing, and room lifecycle were not unrelated edge cases. They were reasons an old client and current authority could diverge.

The tab did not need to wake up empty, and it did not deserve to wake up certain. It needed to preserve what only it knew, learn what had changed elsewhere, and show the person where the two could safely meet.