From 17a41cf29aa4b17bf36f1ad18937d584f3975b32 Mon Sep 17 00:00:00 2001 From: Harvey Zuccon Date: Sat, 30 May 2026 21:21:34 +0200 Subject: [PATCH] Massive speed up during invitation creation at the expense of reliability. Document method for creating a reliability manager of some sort --- readme.md | 52 ++++++++++++++++++++++++++++++++++++++ src/services/app.ts | 2 +- src/services/invitation.ts | 19 +++++++------- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 83d43ff..e8f884f 100644 --- a/readme.md +++ b/readme.md @@ -155,3 +155,55 @@ xo-tui # If not globally installed npm run dev ``` + +## TODO + +### Track invitation sync-server connectivity without blocking the UI + +Each `Invitation` currently owns a `SyncServer` instance for its invitation +identifier. The invitation uses that instance to open an SSE connection, fetch +remote state, and publish local changes. Publish requests are intentionally +fire-and-forget so that invitation actions and the TUI stay responsive when the +sync server is slow or unavailable. + +The tradeoff is that failed background requests and SSE connection changes are +not represented as application state. `SyncServer` already emits `connected`, +`disconnected`, and `error` events, and `Invitation` emits errors from failed +publishes, but there is no app-level owner that aggregates those events. The UI +therefore cannot reliably tell the user that an invitation may only be updated +locally and is not currently syncing with other participants. + +Implement an app-owned `InvitationConnectivityService` (or similarly named +invitation watcher) with the following responsibilities: + +- Register an invitation and its `SyncServer` when `AppService` creates or loads + it, and unregister it when the invitation is removed or stopped. +- Listen for each sync server's `connected`, `disconnected`, and `error` events, + plus invitation publish failures. +- Track connectivity separately from the invitation's business status + (`actionable`, `signed`, `ready`, and so on). Suggested transport states are + `connecting`, `online`, `offline`, and `degraded`, with the last error and + last successful connection timestamp available for diagnostics. +- Expose both per-invitation state and an aggregate app-level state such as + "one or more invitations are not syncing". +- Emit normalized connectivity-change events that the CLI can log and the TUI + can subscribe to without awaiting sync-server requests. + +Keep local persistence and local invitation actions independent from remote +sync health. Failed sync attempts should not freeze normal wallet interaction. +The service should provide a retry path, or observe retry events from the SSE +client, and clear the warning after connectivity recovers. If publish retries +are added, make the retry policy explicit and preserve commit idempotency. + +For UI integration, inject a small notification function or subscribe at the +app-context layer rather than having invitation instances render UI directly. +The first version can show an error dialog when the aggregate state becomes +unhealthy. A less intrusive version can expose the same state as a warning icon +or message in the TUI status bar and reserve dialogs for prolonged failures or +explicit user actions. + +While making this change, consolidate invitation startup ownership. Startup is +currently triggered during `Invitation.create()` and again by +`AppService.createInvitation()`. The watcher should have one clear lifecycle +point so connections, listeners, retries, and cleanup are registered exactly +once. diff --git a/src/services/app.ts b/src/services/app.ts index bb5a885..52c9127 100644 --- a/src/services/app.ts +++ b/src/services/app.ts @@ -173,7 +173,7 @@ export class AppService extends EventEmitter { // Attach listeners before SSE connects so updates are not missed. await this.addInvitation(invitationInstance); - await invitationInstance.start(); + invitationInstance.start(); return invitationInstance; } diff --git a/src/services/invitation.ts b/src/services/invitation.ts index ddacfd6..cafffba 100644 --- a/src/services/invitation.ts +++ b/src/services/invitation.ts @@ -224,12 +224,9 @@ export class Invitation extends EventEmitter { private async publishInvitation( invitation: XOInvitation = this.data, ): Promise { - try { - await this.syncServer.publishInvitation(invitation); - } catch (err) { - // Emit the error event. We might want to throw? but we need a better way of handling errors in the invitation system because we need the invitation to successfully initialize. - this.emit("error", err instanceof Error ? err : new Error(String(err))); - } + this.syncServer.publishInvitation(invitation).catch((error) => { + this.emit("error", error instanceof Error ? error : new Error(String(error))); + }); } /** @@ -374,9 +371,13 @@ export class Invitation extends EventEmitter { * Update the status of the invitation and emit the new single-word status. */ private async updateStatus(): Promise { - const status = await this.computeStatus(); - this.status = status; - this.emit("invitation-status-changed", status); + this.computeStatus().then(status => { + this.status = status; + this.emit("invitation-status-changed", status); + }).catch((error) => { + this.status = `error (${error instanceof Error ? error.message : String(error)})`; + this.emit("error", error instanceof Error ? error : new Error(String(error))); + }); } /**