fix(web): explain why older connections cannot update servers#23374

open8
main
fix/issue-17034
opened4d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago
0 / 4 viewed4 files changedLine totals unavailable

apps/web/src/components/ServerUpdateAction.browser.test.tsx Β· Earlier revision d6055961 Β· right 285

Original diff
@@ -228,4 +268,66 @@ describe("ServerUpdatesAction", () => {
     );
     expect(button(screen, "Update all")!.disabled).toBe(false);
   });
+
+  it("does not offer Update all when no target grants environment:maintain", async () => {
+    using _state = setup();
+    setSession(AsyncResult.success(legacyPairedSession));
+    await using screen = await mount(targets.slice(0, 2));
+
+    expect(button(screen, "Update all")!.disabled).toBe(true);
+    expect(explanation(screen)).toMatch(/pair/i);
+  });
+
+  it("does not ask to pair again when a mixed batch is pending", async () => {
+    using _state = setup();
+    const denied = Atom.make<SessionResult>(AsyncResult.success(legacyPairedSession));
+    testState.sessionAtoms.set("batch-b" as EnvironmentId, denied);
+    const update = Promise.withResolvers<ReturnType<typeof success>>();

apps/server/scripts/issue-17034-fixture.mjs Β· Earlier revision d6055961 Β· right 1–25

Original diff
@@ -0,0 +1,234 @@
+/**
+ * Actual-app update-permission evidence fixture; never starts a server or browser.
+ * Run from any directory with Node 26:
+ *   node apps/server/scripts/issue-17034-fixture.mjs init /tmp/t3-issue-17034-proof
+ *   node apps/server/scripts/issue-17034-fixture.mjs sessions /tmp/t3-issue-17034-private HOME_A HOME_B HOME_C
+ *   node apps/server/scripts/issue-17034-fixture.mjs projects /tmp/t3-issue-17034-project HOME_A HOME_B HOME_C
+ *   node apps/server/scripts/issue-17034-fixture.mjs descriptor-apply /tmp/t3-issue-17034-descriptor.json 16471 16472 16473 16474
+ *   node apps/server/scripts/issue-17034-fixture.mjs descriptor-restore /tmp/t3-issue-17034-descriptor.json 16471 16472 16473 16474
+ *
+ * Coordinate ALL servers sharing this checkout with their owner before apply/restore.
+ * Ports must include every such server. Both commands refuse listening ports and
+ * live runtime PIDs in this worktree's .t3. Apply requires the committed source;
+ * restore requires the exact patched source. Never stage the descriptor or backup.
+ * Metadata advertises an update; it does not install a boot service. Do not infer
+ * successful updates from it. Pending/races/desktop branches still need a harness.
+ *
+ * init is for NEW homes only; existing lead-owned homes can go directly to sessions
+ * or projects. Sessions are minted privately by the supported auth CLI (not fixed
+ * tokens). Re-running sessions creates new credentials, so revoke old sessions via
+ * the auth CLI when finished. Output is private mode-0600 JSON; never publish it.
+ * projects uses the real project CLI and an empty Git repository shared by all homes.
+ * Select its new draft and Auto balance in the real client; no draft/catalog writes.
+ * Labels below are capture inputs, NOT a server-label override: the server descriptor
+ * derives its label from the OS. No supported server-label setting exists here.
+ */

apps/server/scripts/issue-17034-fixture.mjs Β· right 1–25

apps/web/src/components/ServerUpdateAction.tsx Β· Earlier revision d6055961 Β· right 191

Original diff
@@ -133,12 +185,30 @@ export function ServerUpdatesAction({
       target.selfUpdate !== null &&
       (target.selfUpdate !== "desktop-managed" || target.desktopAppUpdate),
   );
+  const sessionStates = useAtomValue(
+    environmentSessionStatesAtom(environmentIdsKey(eligible.map((target) => target.environmentId))),
+  );
+  const permitted = eligible.filter((_, index) => !isUpdatePermissionDenied(sessionStates[index]!));