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.
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') {