supbuddy 3.1.22 → 3.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -19807,7 +19807,7 @@ function cliBuildKind(env3 = process.env) {
19807
19807
  return raw === "host" || raw === "npm" ? raw : "dev";
19808
19808
  }
19809
19809
  function cliVersion(env3 = process.env) {
19810
- return "3.1.22".trim() || "0.0.0-dev";
19810
+ return "3.1.23".trim() || "0.0.0-dev";
19811
19811
  }
19812
19812
  function isDevBuild(env3) {
19813
19813
  return cliBuildKind(env3) === "dev";
@@ -28196,18 +28196,39 @@ var init_store = __esm({
28196
28196
  }));
28197
28197
  },
28198
28198
  setDoctorSummary: (s, report) => set2({ doctorSummary: s, doctorReport: report }),
28199
+ // ── A WRITE THAT CHANGES NOTHING MUST NOT LOOK LIKE A CHANGE ───────────────────
28200
+ //
28201
+ // Both setters used to build a NEW proxyState object unconditionally, so writing the
28202
+ // value that was already there still produced a fresh reference. The store subscriber
28203
+ // publishes on `state.proxyState !== prevState.proxyState`, so a no-op write emitted
28204
+ // `proxy-state-updated` to every client.
28205
+ //
28206
+ // That turned a read into a loop. `port-forwarding:status` — a QUERY — ends by calling
28207
+ // `setPortForwardingEnabled(status.enabled)`, almost always with the value already
28208
+ // held. The renderer re-probed whenever proxyState changed, so: poll → no-op write →
28209
+ // emit → poll. Bounded only by IPC latency, which measured at **51 probes per second**,
28210
+ // each opening a TCP connection to the redirected :443. Under that self-inflicted load
28211
+ // some connects timed out, and each timeout was painted as `NOT ENFORCING` on a machine
28212
+ // answering 30 of 30 sequential probes.
28213
+ //
28214
+ // The renderer's dependency was also fixed, but this is the half that matters: no UI
28215
+ // should be able to induce a feedback loop by reading status. Equality-guard the write,
28216
+ // and the loop cannot form.
28199
28217
  setPortForwardingEnabled: (enabled) => {
28200
- set2((state) => ({
28201
- proxyState: {
28202
- ...state.proxyState,
28203
- portForwardingEnabled: enabled
28204
- }
28205
- }));
28218
+ set2((state) => state.proxyState.portForwardingEnabled === enabled ? {} : { proxyState: { ...state.proxyState, portForwardingEnabled: enabled } });
28206
28219
  },
28207
28220
  // Patch arbitrary proxy fields (restartAttempt/lastExit/networkingDegraded/…) without
28208
28221
  // touching status — used by the supervisor + the drift-gated networking repair.
28222
+ //
28223
+ // Same guard, generalised: the supervisor and the health paths call this on a timer
28224
+ // with values that are usually unchanged, and each such call was a broadcast.
28209
28225
  setProxyFields: (fields) => {
28210
- set2((state) => ({ proxyState: { ...state.proxyState, ...fields } }));
28226
+ set2((state) => {
28227
+ const current = state.proxyState;
28228
+ const changed = Object.entries(fields).some(([k, v]) => current[k] !== v);
28229
+ if (!changed) return {};
28230
+ return { proxyState: { ...state.proxyState, ...fields } };
28231
+ });
28211
28232
  },
28212
28233
  updateSettings: (updates) => {
28213
28234
  set2((state) => ({
@@ -26296,18 +26296,39 @@ const useStore = create((set2, get2) => ({
26296
26296
  }));
26297
26297
  },
26298
26298
  setDoctorSummary: (s, report) => set2({ doctorSummary: s, doctorReport: report }),
26299
+ // ── A WRITE THAT CHANGES NOTHING MUST NOT LOOK LIKE A CHANGE ───────────────────
26300
+ //
26301
+ // Both setters used to build a NEW proxyState object unconditionally, so writing the
26302
+ // value that was already there still produced a fresh reference. The store subscriber
26303
+ // publishes on `state.proxyState !== prevState.proxyState`, so a no-op write emitted
26304
+ // `proxy-state-updated` to every client.
26305
+ //
26306
+ // That turned a read into a loop. `port-forwarding:status` — a QUERY — ends by calling
26307
+ // `setPortForwardingEnabled(status.enabled)`, almost always with the value already
26308
+ // held. The renderer re-probed whenever proxyState changed, so: poll → no-op write →
26309
+ // emit → poll. Bounded only by IPC latency, which measured at **51 probes per second**,
26310
+ // each opening a TCP connection to the redirected :443. Under that self-inflicted load
26311
+ // some connects timed out, and each timeout was painted as `NOT ENFORCING` on a machine
26312
+ // answering 30 of 30 sequential probes.
26313
+ //
26314
+ // The renderer's dependency was also fixed, but this is the half that matters: no UI
26315
+ // should be able to induce a feedback loop by reading status. Equality-guard the write,
26316
+ // and the loop cannot form.
26299
26317
  setPortForwardingEnabled: (enabled) => {
26300
- set2((state) => ({
26301
- proxyState: {
26302
- ...state.proxyState,
26303
- portForwardingEnabled: enabled
26304
- }
26305
- }));
26318
+ set2((state) => state.proxyState.portForwardingEnabled === enabled ? {} : { proxyState: { ...state.proxyState, portForwardingEnabled: enabled } });
26306
26319
  },
26307
26320
  // Patch arbitrary proxy fields (restartAttempt/lastExit/networkingDegraded/…) without
26308
26321
  // touching status — used by the supervisor + the drift-gated networking repair.
26322
+ //
26323
+ // Same guard, generalised: the supervisor and the health paths call this on a timer
26324
+ // with values that are usually unchanged, and each such call was a broadcast.
26309
26325
  setProxyFields: (fields) => {
26310
- set2((state) => ({ proxyState: { ...state.proxyState, ...fields } }));
26326
+ set2((state) => {
26327
+ const current = state.proxyState;
26328
+ const changed = Object.entries(fields).some(([k, v]) => current[k] !== v);
26329
+ if (!changed) return {};
26330
+ return { proxyState: { ...state.proxyState, ...fields } };
26331
+ });
26311
26332
  },
26312
26333
  updateSettings: (updates) => {
26313
26334
  set2((state) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supbuddy",
3
- "version": "3.1.22",
3
+ "version": "3.1.23",
4
4
  "description": "Run multiple Supabase projects at once on custom local domains with HTTPS. A headless CLI and daemon (proxy, DNS, Supabase/Compose lifecycle, MCP) for macOS and Linux.",
5
5
  "keywords": [
6
6
  "supabase",