Skip to content

Windows shell: carry every server request through one HTTP adapter - #68

Merged
JacobStephens2 merged 5 commits into
issue-57from
issue-62
Oct 3, 2026
Merged

JacobStephens2 merged 5 commits into
issue-57from
issue-62

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

The Windows shell now carries every ServerRequest from the core through one HTTP adapter, with no branch per request, and settles each one with ServerResponse.

 Apply(update)
   foreach effect
     …
-    default: CarryRequest(effect)        # 6-way switch → typed SyncApi → 7 settle commands
+    case ServerRequestEffect r: CarryAndSettleAsync(r)
+      response = SyncServer.CarryAsync(r)  # method, base URL + path, Bearer?, JSON body verbatim
+                                           # → ServerResponse { id, status, body }; 0 on no server / error / timeout / cancel
+      dispatcher.TryEnqueue(Send(response))  # on the UI thread, never inside the dispatch that is still running
 apps/windows/Cascade/Services/
 ├── Dto.cs          # ServerRequest / ServerResponse; newDeviceId on deletes; optional platform
-├── SyncApi.cs      # typed client + VerifyResponse / ListeningResponse / SyncHttpException
+└── SyncServer.cs   # SyncConfig (unchanged) + the one adapter
  • The delete commands each make a fresh UUID when the user asks.
  • RequestSignInLink sends platform: "windows", so the emailed link still hands off to the app.
  • The sign-out revoke is now settled like every other request.

Evidence

WinUI can't build on this Linux box, so the evidence comes from three scratch net8.0 projects that compile the shell's own source files. None of them are committed.

  • Adapter and DTO wire, unit tests using a fake HttpMessageHandler:
    serverRequest parses / optional fields absent
    serverResponse, deleteListeningData/deleteAccount newDeviceId, requestSignInLink platform serialize as the core expects
    method + url, bearer header, body verbatim as json, settles with status + body
    no token → no header/content; 401 reported verbatim; network error → 0; cancelled → 0
    no server → 0, nothing sent
    ALL PASS
    
    Before: this didn't compile (SyncServer.cs / ServerRequestEffect missing). After: 15/15 pass.
  • AppViewModel.cs typecheck: compiled against CommunityToolkit.Mvvm 8.4.0, with stubs only for the WinUI types and the audio/SMTC/power/tick services, and warnings treated as errors. Build succeeded.
  • End-to-end against the real core: a Linux build of cascade-uniffi, driven through CoreBridge with the adapter answering from a fake server.
    ok: POST /auth/request body={"email":"a@b.c","platform":"windows"}
    ok: verify sent / signed in
    ok: refresh push sent: PUT /listening auth=sess … / server total adopted: 1h 00m
    ok: DELETE /listening auth=sess / listening deleted
    ok: DELETE /account auth=sess / account deleted, signed out
    ok: revoke sent: POST /auth/logout auth=sess / signed out
    ok: no server: settled, not busy
    ok: 401 signs out: Signed out — sign in again to sync.
    ALL PASS
    
  • cargo test --workspace: everything passes. The core is unchanged.

Merge Danger

Door: two-way

There is no data migration, and the wire only changes between this shell and the core it ships with.

Blast Radius: Windows-only

If something is wrong, sign-in or sync breaks on Windows. Playback is untouched. Nobody has run the real WinUI app on Windows yet (see below).

Unaddressed findings

Standards

  • Speculative Generality: the SyncServer(baseUrl, http) constructor parameters. Kept. They are the seam the adapter tests use: a fake handler for status, body and errors, and an empty base URL for "no sync server". There is no Windows test project in the repo to host those tests yet.
  • Duplicated "no sync server" check (SyncConfig.Available vs CarryAsync's empty-base-URL check). Kept. The ADR requires status 0 with no server whatever produced the request, and the adapter only knows the base URL it was given. Both default to SyncConfig.ApiBase.
  • Middle Man: the view model's carry step. No change. The reviewer called it acceptable, because it owns the settle-on-the-UI-thread rule.
  • Always enqueuing settles (the HasThreadAccess fast path is gone). No change, on purpose. With no server the response is already there while Apply is still running, so settling inline would re-enter it.

(Applied: the view model's CarryAsync was renamed to CarryAndSettleAsync, so it no longer shares a name with SyncServer.CarryAsync.)

Spec

  • "work by hand against the sync server". Not done. There is no Windows machine in this environment. The end-to-end harness above covers the same flows against the real core, but someone still needs to try them by hand on Windows.
  • Scope: the adapter's injectable constructor. Kept, for the reason given under Standards.
  • A settle is lost if TryEnqueue returns false. No change. That only happens while the dispatcher is shutting down at app exit, when there is no core left to settle.
  • Cancellation is only the 15 s HttpClient timeout. No change. The shell has no other cancel path, and a timeout settles with status 0 as required.

Closes #62

🤖 Generated with Claude Code

JacobStephens2 and others added 5 commits October 3, 2026 19:33
The shell's DTOs mirror the core's new wire: ServerRequest replaces the six
request effects, ServerResponse the seven settle commands, the delete
commands carry newDeviceId and RequestSignInLink an optional platform.

SyncServer.CarryAsync sends any ServerRequest verbatim (method, base URL +
path, Bearer token, JSON body) and answers with its ServerResponse: status
and body, or 0 for no sync server, a network error, a timeout or a
cancellation. The typed SyncApi client and its response types are gone;
SyncConfig stays. The view model routes ServerRequest by an explicit case,
settles every request (the sign-out revoke included) on the UI thread via
the dispatcher queue, and sends platform "windows" for the link hand-off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit e3c2a47 into issue-57 Oct 3, 2026
1 check passed
@JacobStephens2
JacobStephens2 deleted the issue-62 branch October 3, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant