All entries

May 7, 2026

5fe1478780c8346832444e49f2f4e599094d3d06e9b709913a6533c81adde5e0
Previous: b3f8e93d Bundle: 89.5 KB

Intents module gains a reactive request signal and response now only exists when an intent is active.

Highlights

  • The intents module now exposes api.intents.request as a reactive signal that tracks the current active intent value.
  • The api.intents.response property is now a getter returning undefined when no intent is active, instead of always being defined.
  • Intent request state is kept synchronized with the internal API via subscription and unsubscribed automatically on page unload.
  • The intents module now accepts a signalFactory parameter, enabling reactive SignalLike signals for intent state management.
  • The intents register method now uses a properly scoped AbortController local to each registration call.
14 files changed +126 -177

Infrastructure Changes

REPORT.md
+6 -6
@@ -1,14 +1,14 @@
# Shopify App Bridge — Unminification Report

Generated: 2026-04-30T08:43:10.147Z
Generated: 2026-05-07T08:51:17.772Z

## Files

| File | Size | Lines | Type |
|------|------|-------|------|
| _bootstrap.js | 31.8KB | 1119 | Infrastructure |
| _bootstrap.js | 29.2KB | 1013 | Infrastructure |
| _remote-ui.js | 6.0KB | 255 | Infrastructure |
| _utilities.js | 69.1KB | 2574 | Infrastructure |
| _utilities.js | 72.2KB | 2707 | Infrastructure |
| _web-vitals.js | 11.6KB | 527 | Infrastructure |
| analytics.js | 211B | 11 | Module |
| app.js | 346B | 15 | Module |
@@ -17,7 +17,7 @@ Generated: 2026-04-30T08:43:10.147Z
| fetch.js | 3.1KB | 94 | Module |
| id-token.js | 661B | 28 | Module |
| index.js | 2.2KB | 48 | Index |
| intents.js | 2.7KB | 89 | Module |
| intents.js | 3.1KB | 106 | Module |
| internal-only.js | 543B | 26 | Module |
| loading.js | 605B | 31 | Module |
| navigation.js | 416B | 19 | Module |
@@ -30,7 +30,7 @@ Generated: 2026-04-30T08:43:10.147Z
| s-app-nav.js | 175B | 10 | Module |
| s-app-window.js | 228B | 12 | Module |
| save-bar.js | 3.4KB | 138 | Module |
| scanner.js | 2.8KB | 101 | Module |
| scanner.js | 2.9KB | 101 | Module |
| scopes.js | 797B | 26 | Module |
| share.js | 1.3KB | 58 | Module |
| shopifyQL.js | 231B | 12 | Module |
@@ -46,7 +46,7 @@ Generated: 2026-04-30T08:43:10.147Z
| user.js | 940B | 37 | Module |
| visibility.js | 973B | 34 | Module |
| web-vitals.js | 1.8KB | 64 | Module |
| **Total** | **169.6KB** | **6449** | |
| **Total** | **170.7KB** | **6493** | |

## Pipeline Stages


modules/_bootstrap.js Truncated
+3 -116
@@ -168,121 +168,7 @@
          removeEventListener: globalThis.removeEventListener.bind(globalThis),
          postMessage: globalThis.parent.postMessage.bind(globalThis.parent),
        });
  const c = (function (t = false) {
  const c = interceptProperty();
    let n = null;
    let e = t ? null : new Set();
    var i = checkPrivateField('value');
    var o = checkPrivateField('callbacks');
    class r {
      constructor(t) {
        var n;
        Object.defineProperty(this, i, {
          writable: true,
          value: undefined,
        });
        Object.defineProperty(this, o, {
          writable: true,
          value: new Set(),
        });
        privateKeyCounter(this, i)[i] = t;
        if (!((n = e) == null)) {
          n.add(this);
        }
      }
      get value() {
        return privateKeyCounter(this, i)[i];
      }
      set value(t) {
        if (t !== privateKeyCounter(this, i)[i]) {
          privateKeyCounter(this, i)[i] = t;
          privateKeyCounter(this, o)[o].forEach((n) => n(t));
        }
      }
      subscribe(t) {
        privateKeyCounter(this, o)[o].add(t);
        return () => {
          privateKeyCounter(this, o)[o].delete(t);
        };
      }
    }
    class a extends r {
      constructor(t) {
        var e;
        super(t);
        if (!((e = n) == null)) {
          e.call(this, t);
        }
      }
      get current() {
        return this.value;
      }
      get value() {
        return c().get.call(this);
      }
      set value(t) {
        throw Error('This signal is read-only');
      }
    }
    let s = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(a.prototype), 'value');
    function c() {
      if (!s || typeof s.get != 'function' || typeof s.set != 'function')
        throw Error('Value descriptor is not found');
      return s;
    }
    function u() {
      e = null;
    }
    return {
      SignalLike: a,
      applyRealSignal: function (t) {
        if (!e) return;
        n = t;
        const i = c();
        Object.setPrototypeOf(a.prototype, t.prototype);
        s = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(a.prototype), 'value');
        Object.defineProperties(a.prototype, {
          $$typeof: {
            configurable: true,
            value: t.prototype.$$typeof,
          },
          ref: {
            configurable: true,
            value: null,
          },
          constructor: {
            configurable: true,
            value: undefined,
          },
          type: {
            configurable: true,
            value: t.prototype.type,
          },
          props: {
            configurable: true,
            get() {
              return {
                data: this,
              };
            },
          },
          K: {
            configurable: true,
            value: 1,
          },
        });
        if (e)
          for (const n of e) {
            const e = i.get.call(n);
            t.call(n, e);
          }
        u();
      },
      sealImplementation: u,
      updateSignalValue: function (t, n) {
        c().set.call(t, n);
      },
    };
  })();
  const l = (function (t) {
    const n = t.decodeSignal;
    return (t) => {
@@ -581,11 +467,11 @@
      })(n.get('shopify-reload'));
    }
  }
    v.resolve(undefined);
    return void (async function () {
      const t = window.name.endsWith('/src');
      const n = Vn(`frame:${L.apiKey}/main`) || Vn(S);
      const n = Vn(`frame:${S.apiKey}/main`) || Vn(C);
      if (n) {
        window.opener = n;
        window.fetch = n.fetch;
@@ -714,6 +600,7 @@
    );
    const i = (function (t) {
      const n = {};
      let e = false;
      return {
        async set(e) {
          const i = await t;

Diff truncated at 200 lines

modules/_utilities.js Truncated
+48 -3
@@ -101,12 +101,12 @@ const R = [
  'UnexpectedAction',
  'UnsupportedOperation',
];
function $(t, n, e) {
function subscribeAllErrors(t, n, e) {
  R.forEach((i) => {
    t.subscribe('Error.' + i, n, e);
  });
}
  let t;
  let n = false;
  const promise = new Promise((n) => {
@@ -125,7 +125,7 @@ function _() {
    },
  };
}
function subscribeAllErrors() {
function _() {
  let t = Promise.resolve();
  const n = {};
  return {
@@ -567,10 +567,126 @@ var createPrivateKey = 0;
function checkPrivateField(t) {
  return `__private_${createPrivateKey++}_${t}`;
}
const interceptProperty = Symbol();
function interceptProperty(t = false) {
  let n = null;
  let e = t ? null : new Set();
  var i = checkPrivateField('value');
  var o = checkPrivateField('callbacks');
  class r {
    constructor(t) {
      var n;
      Object.defineProperty(this, i, {
        writable: true,
        value: undefined,
      });
      Object.defineProperty(this, o, {
        writable: true,
        value: new Set(),
      });
      privateKeyCounter(this, i)[i] = t;
      if (!((n = e) == null)) {
        n.add(this);
      }
    }
    get value() {
      return privateKeyCounter(this, i)[i];
    }
    set value(t) {
      if (t !== privateKeyCounter(this, i)[i]) {
        privateKeyCounter(this, i)[i] = t;
        privateKeyCounter(this, o)[o].forEach((n) => n(t));
      }
    }
    subscribe(t) {
      privateKeyCounter(this, o)[o].add(t);
      return () => {
        privateKeyCounter(this, o)[o].delete(t);
      };
    }
  }
  class a extends r {
    constructor(t) {
      var e;
      super(t);
      if (!((e = n) == null)) {
        e.call(this, t);
      }
    }
    get current() {
... (truncated)

Diff truncated at 200 lines

modules/_web-vitals.js Truncated
+10 -10
@@ -65,42 +65,42 @@ var be = function (t) {
    }
  };
};
  return document.visibilityState !== 'hidden' || document.prerendering ? 1 / 0 : 0;
};
  }
};
var Ae = function () {
  addEventListener('visibilitychange', ge, true);
  addEventListener('prerenderingchange', ge, true);
};
var Ee = function () {
  removeEventListener('visibilitychange', ge, true);
  addEventListener('visibilitychange', Ae, true);
  removeEventListener('prerenderingchange', ge, true);
  addEventListener('prerenderingchange', Ae, true);
};
var ke = function () {
  if (ye < 0) {
  removeEventListener('visibilitychange', Ae, true);
    ye = ve();
  removeEventListener('prerenderingchange', Ae, true);
    Ae();
};
    ue(function () {
var Pe = function () {
  if (ve < 0) {
    ve = ge();
    Ee();
    le(function () {
      setTimeout(function () {
      }, 0);
    });
  }
  return {
    get firstHiddenTime() {
    },
  };
};
  if (document.prerendering) {
    addEventListener(
      'prerenderingchange',

Diff truncated at 200 lines

Module Changes

modules/fetch.js
+3 -3
@@ -3,8 +3,8 @@
 * Intercepted fetch with auth headers and session token refresh
 */

// Registry entry referenced as: It
// Registry entry referenced as: Mt
  const i = self.fetch;
  async function o(t, n) {
    const e = new Headers(n.headers).get('Shopify-Challenge-Required');
@@ -63,16 +63,16 @@ const It = ({ api, protocol, internalApiPromise }) => {
    const w = request.clone();
    let b;
    if (p?.fetch) {
      const t = await Tt(request);
      const t = await restoreProperty(request);
      const n = await p.fetch(t);
    } else b = await i(request);
    if (b.headers.get('X-Shopify-Retry-Invalid-Session-Request') && m) {
      w.headers.set('Authorization', 'Bearer ' + (await api.idToken()));
      if (p?.fetch) {
        const t = await Tt(w);
        const t = await restoreProperty(w);
        const n = await p.fetch(t);
      } else b = await i(w);
    }
    const y = b.headers.get('X-Shopify-API-Request-Failure-Reauthorize-Url');
modules/intents.js
+47 -30
@@ -3,7 +3,7 @@
 * Intent-based navigation and deep linking
 */

const intentsModule = ({ api, protocol, internalApiPromise }) => {
const intentsModule = ({ api, protocol, internalApiPromise, signalFactory }) => {
  api.data.intent = (function () {
    try {
      const t = new URLSearchParams(location.search).get('intent');
@@ -21,17 +21,30 @@ const intentsModule = ({ api, protocol, internalApiPromise }) => {
      console.error(err);
    }
  })();
  const abortController = new AbortController();
  const o = new signalFactory.SignalLike(api.data.intent ?? null);
  let r;
  async function a() {
    const t = (await internalApiPromise) || {};
    if (
      o.value == null ||
      typeof t.intents?.response?.ok != 'function' ||
      typeof t.intents?.response?.error != 'function' ||
      typeof t.intents?.response?.closed != 'function'
    )
      throw Error('Cannot respond to intent in this context');
    return t.intents.response;
  }
  api.intents = {
    request: o,
    register(t) {
      const e = new URLSearchParams(location.search).get('step_reference') || '';
      const i = new AbortController();
      const abortController = new AbortController();
      protocol.send('AppFrame.requestProperties');
      protocol.subscribe(
        'AppFrame.propertiesEvent',
        ({ properties }) => {
          const o = (function (t, n, e) {
            return new xt('configure', 'gid://flow/stepReference/' + t, n, () =>
            return new safeAsyncCall('configure', 'gid://flow/stepReference/' + t, n, () =>
              e.send('AppFrame.navigateBack'),
            );
          })(
@@ -44,45 +57,49 @@ const intentsModule = ({ api, protocol, internalApiPromise }) => {
          t(o);
        },
        {
          signal: i.signal,
          signal: abortController.signal,
        },
      );
      return () => i.abort();
      return () => abortController.abort();
    },
    async invoke(t, n) {
      const i = await internalApiPromise;
      if (!i) throw Error('Cannot invoke intent');
      if (!i.intents?.invoke || typeof i.intents.invoke != 'function')
        throw Error('Intents are not supported');
      return new safeAsyncCall(i.intents.invoke(t, n));
      return new WINDOW_TARGETS(i.intents.invoke(t, n));
    },
  };
  if (ot()) {
  internalApiPromise.then((t) => {
    async function o() {
    const n = t?.intents?.request;
      const t = (await internalApiPromise) || {};
    if (n && typeof n.subscribe == 'function') {
      if (
      signalFactory.updateSignalValue(o, n.value ?? null);
        typeof t.intents?.response?.ok != 'function' ||
      r?.();
        typeof t.intents?.response?.error != 'function' ||
      r = n.subscribe((t) => {
        typeof t.intents?.response?.closed != 'function'
        signalFactory.updateSignalValue(o, t ?? null);
      )
      });
        throw Error('Cannot respond to intent in this context');
      if (abortController.signal.aborted) throw Error('Cannot resolve an intent multiple times');
      abortController.abort();
      return t.intents.response;
    }
    api.intents.response = {
  });
      async ok(t) {
  globalThis.addEventListener?.('beforeunload', () => r?.(), {
        const n = await o();
    once: true,
        return await n.ok(t);
  });
      },
  const s = {
      async error(t, n) {
    async ok(t) {
        return await e.error(t, n);
      return await n.ok(t);
      async closed() {
    async error(t, n) {
        return await t.closed();
      return await e.error(t, n);
    };
    async closed() {
  }
      const t = await a();
      return await t.closed();
    },
  };
  Object.defineProperty(api.intents, 'response', {
    configurable: true,
    enumerable: true,
    get: () => (o.value == null ? undefined : s),
  });
};

modules/resource-picker.js
+1 -1
@@ -80,7 +80,7 @@ const resourcePickerModule = ({ api, protocol, internalApiPromise }) => {
            signal: p,
          },
        );
        $(
        subscribeAllErrors(
          protocol,
          (t) => {
            m();

modules/s-app-nav.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <s-app-nav> element for app navigation
 */

// Registry entry referenced as: hn
// Registry entry referenced as: pn


modules/s-app-window.js
+1 -1
@@ -3,9 +3,9 @@
 * Custom <s-app-window> element for app window management
 */

// Registry entry referenced as: Hn
// Registry entry referenced as: Kn
  variantLock: 'app-window',
});


modules/scanner.js
+2 -2
@@ -22,7 +22,7 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
              );
            }
            function s() {
              $(protocol, a, {
              subscribeAllErrors(protocol, a, {
                signal: r,
                id: i,
              });
@@ -50,14 +50,14 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
            protocol.subscribe(
              'getState',
              ({ features }) => {
                  s();
                } else {
                  (function () {
                    const e = new AbortController();
                    abortController.signal.addEventListener('abort', () => e.abort());
                    $(
                    subscribeAllErrors(
                      protocol,
                      (t) => {
                        e.abort();
modules/share.js
+1 -1
@@ -23,7 +23,7 @@ const shareModule = ({ protocol, internalApiPromise }) => {
            }),
          );
        }
        $(protocol, u, {
        subscribeAllErrors(protocol, u, {
          signal: c,
          id: i,
        });

modules/title-bar.js
+1 -1
@@ -24,16 +24,16 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
  function r(t) {
    const n = document.querySelector('s-page');
    if (n) {
      const e = `${Rn}, ${$n}, ${_n}`;
      const e = `${Fn}, ${$n}, ${_n}`;
      if (i) return void i.click();
      const o = Array.from(document.querySelectorAll('s-menu, s-button-group'));
      for (const n of o) {
        if (e) return void e.click();
      }
    }
    i?.click();
  }
  function a(t) {
modules/ui-modal.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <ui-modal> element
 */

// Registry entry referenced as: Zn
// Registry entry referenced as: te


modules/ui-nav-menu.js
+1 -1
@@ -3,7 +3,7 @@
 * Custom <ui-nav-menu> element
 */

// Registry entry referenced as: te
// Registry entry referenced as: ne