All entries

September 11, 2026

f524cbbfe0a9f1d3f3ab24653f2f93c52c40f80912e502f7d36032cfbe8ee22c
Previous: b7e0f69c Bundle: 95.3 KB

Title bar module now hides duplicate native action elements via injected CSS and skips empty secondary action groups.

Highlights

  • The title-bar module now hides duplicate breadcrumb, primary, secondary, and accessory action elements by injecting a stylesheet instead of manipulating hideElements directly.
  • Secondary title bar actions are now only added to the update payload when the resolved secondary actions array is non-empty, avoiding empty button groups.
  • The MutationObserver that watches for title bar changes now matches against a list of relevant tag names instead of a single hardcoded S-PAGE check.
  • Title bar and breadcrumb button click handling now resolves the page element through a shared helper function instead of directly querying s-page.
23 files changed +88 -63

Infrastructure Changes

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

Generated: 2026-08-19T08:08:19.816Z
Generated: 2026-09-11T08:04:09.679Z

## Files

@@ -8,29 +8,29 @@ Generated: 2026-08-19T08:08:19.816Z
|------|------|-------|------|
| _bootstrap.js | 30.1KB | 1052 | Infrastructure |
| _remote-ui.js | 6.1KB | 257 | Infrastructure |
| _utilities.js | 76.2KB | 2858 | Infrastructure |
| _utilities.js | 77.7KB | 2912 | Infrastructure |
| _web-vitals.js | 11.6KB | 527 | Infrastructure |
| analytics.js | 211B | 11 | Module |
| app.js | 346B | 15 | Module |
| client.js | 303B | 15 | Module |
| environment.js | 241B | 14 | Module |
| fetch.js | 3.0KB | 94 | Module |
| id-token.js | 908B | 34 | Module |
| id-token.js | 941B | 34 | Module |
| index.js | 2.2KB | 48 | Index |
| intents.js | 3.1KB | 106 | Module |
| internal-only.js | 542B | 26 | Module |
| internal-only.js | 534B | 26 | Module |
| loading.js | 605B | 31 | Module |
| navigation.js | 408B | 19 | Module |
| picker.js | 451B | 18 | Module |
| polaris.js | 240B | 12 | Module |
| pos.js | 5.7KB | 248 | Module |
| print.js | 558B | 25 | Module |
| print.js | 556B | 25 | Module |
| resource-picker.js | 2.8KB | 117 | Module |
| reviews.js | 365B | 16 | Module |
| 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 | 262B | 12 | Module |
@@ -38,7 +38,7 @@ Generated: 2026-08-19T08:08:19.816Z
| sidekick.js | 5.3KB | 179 | Module |
| support.js | 508B | 21 | Module |
| telemetry.js | 935B | 35 | Module |
| title-bar.js | 7.6KB | 290 | Module |
| title-bar.js | 8.4KB | 317 | Module |
| toast.js | 1.6KB | 71 | Module |
| tools.js | 2.1KB | 75 | Module |
| ui-modal.js | 153B | 10 | Module |
@@ -46,7 +46,7 @@ Generated: 2026-08-19T08:08:19.816Z
| user.js | 940B | 37 | Module |
| visibility.js | 973B | 34 | Module |
| web-vitals.js | 1.9KB | 66 | Module |
| **Total** | **177.5KB** | **6752** | |
| **Total** | **179.8KB** | **6833** | |

## Pipeline Stages


modules/_bootstrap.js Truncated
+1 -1
@@ -234,15 +234,15 @@
              type: t.type,
              lastModified: t.lastModified,
            },
            content: interceptProperty(t),
            content: restoreProperty(t),
          };
        } else {
          if (Array.isArray(t)) {
          } else {
            if (f(t)) {
                return e;
              }, {});
            }

Diff truncated at 200 lines

modules/_remote-ui.js
+1 -1
@@ -240,9 +240,9 @@ function p(t) {
}
class h extends Error {
  constructor(t) {
    super(
      `No resolver found for call ID: ${n}${e ? ' Error: ' + e : ''}${o == null ? '' : ' Result: ' + JSON.stringify(o)}`,
      `No resolver found for call ID: ${n}${e ? ' Error: ' + e : ''}${i == null ? '' : ' Result: ' + JSON.stringify(i)}`,
    );
    this.callId = undefined;
    this.error = undefined;
modules/_utilities.js Truncated
+4 -5
@@ -102,12 +102,12 @@ const U = [
  'UnexpectedAction',
  'UnsupportedOperation',
];
function _(t, n, e) {
function subscribeAllErrors(t, n, e) {
  });
}
function subscribeAllErrors() {
function U() {
  let t;
  let n = false;
  const promise = new Promise((n) => {
@@ -135,9 +135,9 @@ function createDeferred() {
    },
    has: (t) => !!n[t],
    add(e) {
      const o = subscribeAllErrors();
      const i = U();
    },
    resolve(t) {
      const e = n[t];
@@ -183,64 +183,64 @@ function N({ keys, held, handler, keyEvent: o = 'keydown' }) {
    );
  };
  const s = () => {
  };
    capture: true,
  });
  return () => {
      capture: true,
    });
  };
}
  const t = window.shopify.config.host;
  return 'https://' + atob(t);
}
const q = Symbol();
const W = Symbol();
const H = Symbol();
const V = 'data-save-bar';
const z = 'data-discard-confirmation';
const set = new Set();
  for (const t of set) t.settle();
}
function Q(t, { onChange, filter: e = () => true }) {
  function r() {
    return rt(t).filter((t) => nt(t) && e(t));
  }
  function a(t) {
  }
  function s() {
    const e = r();
    let o = false;
    let i = false;
    let o = false;
    for (const t of e) {
      if (o) break;
... (truncated)

Diff truncated at 200 lines

modules/_web-vitals.js Truncated
+0 -1
@@ -113,79 +113,79 @@ var Ne = function (t) {
    t();
  }
};
  n = n || {};
    var e;
      t.forEach(function (t) {
        if (t.name === 'first-contentful-paint' /* FCP PerformanceObserver entry type */) {
          r.disconnect();
            e(true);
          }
        }
      });
    });
    if (r) {
          e(true);
        });
      });
    }
  });
};
  n = n || {};
      var e;
      var i = 0;
... (truncated)

Diff truncated at 200 lines

Module Changes

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

// Registry entry referenced as: SHOPIFY_PROTOCOLS
// Registry entry referenced as: deepClone
const SHOPIFY_PROTOCOLS = ({ api, protocol, internalApiPromise }) => {
const deepClone = ({ api, protocol, internalApiPromise }) => {
    try {
      return await api.idToken();
    } catch (err) {
@@ -23,7 +23,7 @@ const SHOPIFY_PROTOCOLS = ({ api, protocol, internalApiPromise }) => {
          verified: false,
        };
  }
  deepClone(globalThis, 'fetch', async function (a, s) {
  safeAsyncCall(globalThis, 'fetch', async function (a, s) {
    const request = new Request(a instanceof Request ? a.clone() : a, s);
    const { appOrigins: u = [] } = api.config;
    const url = new URL(request.url);
@@ -67,27 +67,27 @@ const SHOPIFY_PROTOCOLS = ({ api, protocol, internalApiPromise }) => {
      request.headers.set('Accept-Language', api.config.locale);
    }
    const w = request.clone();
      if (t) {
        w.headers.set('Authorization', 'Bearer ' + t);
      }
    }
      protocol.send('Navigation.redirect.remote', {
      });
      return new Promise(() => {});
    }
    if (f) {
    }
  });
};

const fetchModule = SHOPIFY_PROTOCOLS;
const fetchModule = deepClone;

modules/id-token.js
+3 -3
@@ -3,20 +3,20 @@
 * Session token (ID token) request/response
 */

// Registry entry referenced as: Ut
// Registry entry referenced as: ALL_PROTOCOLS
const Ut = ({ api, protocol, internalApiPromise }) => {
const ALL_PROTOCOLS = ({ api, protocol, internalApiPromise }) => {
  api.idToken = async function () {
    const { idToken: t } = (await internalApiPromise) || {};
      ? await t()
      : await new Promise((t, e) => {
            e(Error('idToken unavailable: host did not respond in time'));
          }, 6e4);
          protocol.subscribe(
            'SessionToken.respond',
            ({ sessionToken }) => {
              t(sessionToken);
            },
            {
@@ -25,9 +25,9 @@ const Ut = ({ api, protocol, internalApiPromise }) => {
          );
          protocol.send('SessionToken.request');
        });
  };
};

const idTokenModule = Ut;
const idTokenModule = ALL_PROTOCOLS;

modules/intents.js
+1 -1
@@ -43,8 +43,8 @@ const intentsModule = ({ api, protocol, internalApiPromise, signalFactory }) =>
      protocol.subscribe(
        'AppFrame.propertiesEvent',
        ({ properties }) => {
            return new WINDOW_TARGETS('configure', 'gid://flow/stepReference/' + t, n, () =>
            return new Ut('configure', 'gid://flow/stepReference/' + t, n, () =>
              e.send('AppFrame.navigateBack'),
            );
          })(
modules/internal-only.js
+1 -1
@@ -6,10 +6,10 @@
const internalOnlyModule = ({ api, internalApiPromise }) => {
  const e = {
    async show(t, e) {
      const o = SIMULATING_CLICK(e);
      const i = parseUrl(e);
      }
    },
    async hide(t) {

modules/pos.js Truncated
+2 -2
@@ -5,10 +5,10 @@

const posModule = ({ api, protocol }) => {
  const e = new Set();
    const abortController = new AbortController();
    const a = subscribeAllErrors();
    const a = U();
    protocol.subscribe(
      'Cart.update',
      ({ data }) => {
@@ -17,22 +17,22 @@ const posModule = ({ api, protocol }) => {
      },
      {
        signal: abortController.signal,
      },
    );
    protocol.send('Cart.' + t, {
    });
    const s = await a.promise;
    e.forEach((t) => t(s));
  }
    cart: {
      async fetch() {
        const abortController = new AbortController();
        const o = subscribeAllErrors();
        const i = U();
        protocol.send('Cart.fetch', {
          id: t,
        });

Diff truncated at 200 lines

modules/print.js
+2 -2
@@ -4,13 +4,13 @@
 */

const printModule = ({ protocol, internalApiPromise }) => {
    deepClone(self, 'print', function () {
    safeAsyncCall(self, 'print', function () {
      const e = document.scrollingElement?.scrollHeight || document.body.offsetHeight;
      parseUrl(async () => {
      Dt(async () => {
            height: e,
          });
        } else {

modules/resource-picker.js
+1 -1
@@ -78,7 +78,7 @@ const resourcePickerModule = ({ api, protocol, internalApiPromise }) => {
            signal: h,
          },
        );
        _(
        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: kn
// Registry entry referenced as: xn


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

// Registry entry referenced as: re
// Registry entry referenced as: ve
  variantLock: 'app-window',
});


modules/scanner.js
+2 -2
@@ -22,9 +22,9 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
              );
            }
            function s() {
              _(protocol, a, {
              subscribeAllErrors(protocol, a, {
                signal: r,
              });
              protocol.subscribe(
                'Scanner.capture',
@@ -39,25 +39,25 @@ const scannerModule = ({ api, protocol, internalApiPromise }) => {
                  }
                },
                {
                  signal: r,
                },
              );
              protocol.send('Scanner.open.camera', {
              });
            }
            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
+2 -2
@@ -4,15 +4,15 @@
 */

const shareModule = ({ protocol, internalApiPromise }) => {
  const e = navigator.share;
  deepClone(navigator, 'share', async function (o) {
  safeAsyncCall(navigator, 'share', async function (i) {
      return new Promise((n, e) => {
        const abortController = new AbortController();
        const { signal: c } = abortController;
        function u(t) {
@@ -23,9 +23,9 @@ const shareModule = ({ protocol, internalApiPromise }) => {
            }),
          );
        }
        _(protocol, u, {
        subscribeAllErrors(protocol, u, {
          signal: c,
        });
        protocol.subscribe(
          'Share.close',
modules/sidekick.js
+3 -3
@@ -108,69 +108,69 @@ const sidekickModule = ({ api, internalApiPromise, rpcEventTarget }) => {
    },
    registerToolHandler: (t) => {
      let e;
        e?.();
        u(e);
        e = undefined;
      }
      parseUrl(async () => {
      Dt(async () => {
        const { sidekick: r } = (await internalApiPromise) || {};
        if (!r || typeof r.registerToolHandler != 'function')
          throw Error('Sidekick API is not available');
        e = await r.registerToolHandler(t);
        s(e);
        }
      });
    },
    registerContextCallback: async (t) => {
      let e;
        e?.();
        u(e);
        e = undefined;
      }
      parseUrl(async () => {
      Dt(async () => {
        const { sidekick: r } = (await internalApiPromise) || {};
        if (!r || typeof r.registerContextCallback != 'function')
          throw Error('Sidekick API is not available');
        e = await r.registerContextCallback(t);
        s(e);
        }
      });
    },
    registerNavigationReadinessProvider: (t) => {
      let e;
        e?.();
        u(e);
        e = undefined;
      }
      parseUrl(async () => {
      Dt(async () => {
        const { sidekick: r } = (await internalApiPromise) || {};
          if (!r || typeof r.registerNavigationReadinessProvider != 'function')
            throw Error('Sidekick API is not available');
          e = await r.registerNavigationReadinessProvider(t);
          s(e);
          }
        }
      });
      return function () {
      };
    },
  };

modules/title-bar.js Truncated
+42 -15
@@ -10,41 +10,41 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
      'getState',
      ({ features }) => {
        const e = features?.MarketingExternalActivityTopBar || {};
      },
      {
        once: true,
      },
    );
  });
    if (e) return e.getAttribute('title') ?? document.title;
  }
  function r(t) {
    const n = document.querySelector('s-page');
    const n = Xt(document);
    if (n) {
      const e = `${Vn}, ${zn}, ${Gn}`;
      const e = `${re}, ${ae}, ${se}`;
      const i = Array.from(document.querySelectorAll('s-menu, s-button-group'));
      const o = Array.from(document.querySelectorAll(Nn));
        if (e) return void e.click();
      }
    }
  }
  function a(t) {
    return {
      label: e,
      ...(o && {
        icon: o,
      }),
      ...(i && {
      }),
      ...(o && {
        tone: o,
      }),
      ...(c !== undefined && {
        pressed: c,
@@ -54,22 +54,48 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
      onAction: () => r?.(n),
    };
  }
  async function s() {
  let s;
  function c(t) {
    if (!s) {
      if (!t) return;
      s = document.createElement('style');
      (document.head ?? document.documentElement)?.appendChild(s);
    }
    if (t) {
      s.textContent =
        (function (t) {
          const n = [t];
          const e = t.startsWith('s-internal-');
          return [
            Jt('breadcrumb-actions', jn, n),
            Jt('primary-action', jn, n),
            Jt('secondary-actions', qn, n),
            Jt('accessory', undefined, n),
            ...SIMULATING_CLICK('s-menu')
              .filter((t) => t.startsWith('s-internal-') === e)
              .map((t) => t + '.title-bar-menu'),
          ].join(', ');
        })(t) + ' { display: none !important; }';
    }
    s.disabled = !t;
  }
  async function u() {
    let r;
    (function () {
      if (t && t.length) {
        for (const n of t)
            e = n;
            break;
          }
      } else e = undefined;
    })();
    if (e) {
      const { primaryButton: t, secondaryButtons: n, breadcrumb: o } = e.buttons?.() ?? {};
      c();
      const { primaryButton: t, secondaryButtons: n, breadcrumb: i } = e.buttons?.() ?? {};
      r = {
      };
      if (t) {
        r.buttons = Object.assign(r.buttons ?? {}, {
@@ -81,36 +107,38 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
          secondary: n,
        });
      }
        r.breadcrumbs = {
          id: 'breadcrumb',
        };
      }
    } else {
        hideElements: true,
        hideElements: false,
        context: document,
      });
      const n = Xt(document);
      c(t && n?.localName === qt ? qt : undefined);
      if (t) {
        r = {
        };
        if (t.breadcrumb) {
        }
        if (t.primaryAction) {
          r.buttons = Object.assign(r.buttons ?? {}, {
            primary: n,
          });
        }
        if (t.secondaryActions) {
        const n = Yn(t.secondaryActions ?? [], {
          const n = Fn(t.secondaryActions, {
          hideElements: false,
            hideElements: true,
          context: document,
            context: document,
        });
          });
        if (n.length) {
          r.buttons = Object.assign(r.buttons ?? {}, {
            secondary: n,
          });
@@ -163,26 +191,26 @@ const titleBarModule = ({ protocol, internalApiPromise }) => {
      } else protocol.send('TitleBar.update', r);
    }
  }
  Object.defineProperty(document, 'title', {
    set(t) {
      s();
... (truncated)

Diff truncated at 200 lines

modules/toast.js
+2 -2
@@ -5,15 +5,15 @@

const toastModule = ({ api, protocol, internalApiPromise }) => {
  api.toast = {
      parseUrl(async () => {
      Dt(async () => {
        const { toast: r } = (await internalApiPromise) || {};
        if (r?.show)
          await r.show(t, {
          });
        else {
          const abortController = new AbortController();
@@ -52,13 +52,13 @@ const toastModule = ({ api, protocol, internalApiPromise }) => {
          });
        }
      });
    },
    hide(t) {
      parseUrl(async () => {
      Dt(async () => {
        } else {
          protocol.send('Toast.clear', {
            id: t,

modules/tools.js
+3 -3
@@ -4,16 +4,16 @@

const toolsModule = async ({ api, internalApiPromise }) => {
  const map = new Map();
  api.tools = {
      const abortController = new AbortController();
      parseUrl(async () => {
      Dt(async () => {
        if (abortController.signal.aborted) return;
        map.get(t)?.();
          t,
          (function (t) {
            return async (...n) => {
@@ -46,25 +46,25 @@ const toolsModule = async ({ api, internalApiPromise }) => {
    },
    unregister(t) {
      const abortController = new AbortController();
      map.get(t)?.();
      map.delete(t);
      parseUrl(async () => {
      Dt(async () => {
        const { tools: e } = (await internalApiPromise) || {};
        if (!abortController.signal.aborted) {
          if (!e || typeof e.unregister != 'function') throw Error('Tools API is not available');
          await e.unregister(t);
        }
      }).finally(() => {
      });
    },
    clear() {
      map.forEach((t) => t());
      map.clear();
      parseUrl(async () => {
      Dt(async () => {
        const { tools: t } = (await internalApiPromise) || {};
        if (!t || typeof t.clear != 'function') throw Error('Tools API is not available');
        await t.clear();

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

// Registry entry referenced as: he
// Registry entry referenced as: Le


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

// Registry entry referenced as: me
// Registry entry referenced as: Ie


modules/web-vitals.js
+2 -2
@@ -25,25 +25,25 @@ const webVitalsModule = async ({ api, protocol, internalApiPromise, rpcEventTarg
    reportAllChanges: c,
  };
  let l = true;
    r[t](
        if (c && typeof s?.report == 'function') {
          s.report({
            version: Mt,
            version: SHOPIFY_PROTOCOLS,
          });
        } else {
          protocol.send('WebVitals.' + t, {
            version: Mt,
            version: SHOPIFY_PROTOCOLS,
          });
        }
          l = false;
          const { perceivedPerformance: t } = (await internalApiPromise) || {};
          if (t?.appIsReady && typeof t.appIsReady == 'function') {