Skip to content

webapp: the settings dialogs stop holding on a spinner - #220

Draft
pythonlearner1025 wants to merge 1 commit into
mainfrom
feat/optimistic-held-dialogs
Draft

webapp: the settings dialogs stop holding on a spinner#220
pythonlearner1025 wants to merge 1 commit into
mainfrom
feat/optimistic-held-dialogs

Conversation

@pythonlearner1025

Copy link
Copy Markdown
Member

What part this touches

Two dialogs in the webapp settings surface.

WorkspaceDetailsDialog.tsx saves a workspace's name, default machine type,
auto-provision flag and agent rule. It also adds and removes the repositories a
workspace clones.

AgentRulesPicker.tsx creates, edits and deletes an agent rule, and selects
which rule a workspace uses.

Both call the control plane and wait. This makes them answer at once.

Stacked on #205. Review that one first.

The gap

A user presses Save and the button says "Saving…" until the network answers.
Nothing else on screen changes. The same holds for adding a repository,
removing one, and every agent-rule action.

Five surfaces behave this way:

  • Workspace settings save waits for updateWorkspace, then dispatches.
  • Add repository waits for the returned repository list.
  • Remove repository leaves the row on screen until the DELETE succeeds.
  • Create and update an agent rule wait for the PUT, even though the client
    already holds the rule's UUID.
  • Delete an agent rule keeps the rule and its editor open until the DELETE
    succeeds.

This is item 3 of the audit on #205: dialogs that hold on a spinner.

The fix

  • Workspace settings dispatch on submit, then reconcile with the workspace the
    server returns.
  • A rejected save restores the snapshot and shows the error.
  • Adding a repository inserts a pending row at once. The response replaces the
    whole list. A rejection removes the pending row and reports.
  • Removing a repository removes the row at once, and keeps the row and its
    index so a rejection can put it back.
  • Creating or updating an agent rule inserts, selects and closes at once, using
    the UUID the client already has. The canonical rule replaces it.
  • Deleting a rule removes it, resets the selection and closes at once.
  • Every rejection restores the snapshot and shows a visible error.

The risk trade

Requests are not serialized. Two saves in flight together can settle out of
order, so an older response can briefly overwrite a newer optimistic view. The
next poll corrects it.

The alternative was a per-surface request queue. That is more machinery than
these dialogs justify: both are single-user settings screens where two writes
racing needs deliberate effort. Recording the limit here is the honest trade.
If it turns out to bite, the fix is a sequence number on the write, not a
queue.

Agent rules reuse the UUID the client already generates, so there is no
placeholder id to reconcile. That is why this PR needs nothing like the
pendingCreate machinery #205 added for workspaces.

Tests

Six tests in packages/webapp/test/WorkspaceDetailsDialog.test.tsx, one per
surface. Each drives a deferred promise, so it asserts the screen changed
BEFORE the request settled, and then asserts the restore on rejection. Both
directions, or the test proves nothing.

Each was seen failing with its own change reverted. For example:

FAIL  commits settings immediately and restores the snapshot with an error on rejection
AssertionError: expected 'Saving…' to be 'Save settings'
FAIL  removes a repository immediately and restores its index with an error on rejection
AssertionError: expected <button aria-label="Remove acme/tools"> to be null

Real counts on this branch: 116 webapp files pass, 1011 tests pass, 61 skip.
One it.fails(...) in lody-toaster.test.tsx reports as an expected fail, as
it does on main.

npm run lint:gate passes at baseline. 66 anti-slop findings, 0 house
findings, 8 max-lines warnings. None moved.

npm run typecheck -w @blitzos/webapp passes.

Deploy

The webapp bundle ships inside the control-plane worker. This branch is stacked
on #205 and must not merge before it.

After #205 merges, rebase this onto main and merge it. Merging to main runs
.github/workflows/canary.yml, which deploys the worker to canary. No box code
changed.

Verify after the merge:

curl -s https://blitz-control-plane.minjunesv0.workers.dev/version

Then open a workspace's settings, press Save, and watch the dialog answer
before the request finishes.

Rollback:

git revert <merge-sha>
git push origin main

@pythonlearner1025
pythonlearner1025 force-pushed the feat/optimistic-held-dialogs branch from ab0774c to 936777f Compare September 5, 2026 07:15
@pythonlearner1025
pythonlearner1025 changed the base branch from feat/optimistic-members-machines to main September 5, 2026 07:17
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