From mida-skills
Use when changing, reviewing, or adding code in the mida-extension repository — Shopify theme app extension (liquid blocks, recorder/survey/GDPR modules), web pixel extension, webpack build config, rrweb customization, or the local Koa static server.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mida-skills:mida-extensionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill before modifying `mida-extension`. This is a **Shopify Extension** (Webpack + vanilla JS) that injects session recording, survey, GDPR consent, and heatmap tracking into merchant storefronts.
Use this skill before modifying mida-extension. This is a Shopify Extension (Webpack + vanilla JS) that injects session recording, survey, GDPR consent, and heatmap tracking into merchant storefronts.
find mida-extension -maxdepth 4 -type f | grep -v node_modules | sort
git -C mida-extension status --short
mida-extension/
├── extensions/
│ ├── theme-app-extension/
│ │ ├── blocks/
│ │ │ └── mida_recorder.liquid # Theme block entry — injects scripts + global vars
│ │ ├── assets/ # BUILD OUTPUT (.msr.js, .css) — generated by webpack
│ │ ├── locales/ # i18n
│ │ └── shopify.extension.toml
│ └── web-pixel/
│ ├── src/index.js # Web pixel entry (checkout events)
│ ├── dist/web-pixel.js # Compiled
│ └── shopify.extension.toml
├── scripts/
│ ├── modules/
│ │ ├── recorder/
│ │ │ ├── index.js # IIFE singleton
│ │ │ ├── load/ # init.load.js, event.load.js, recorder.load.js, observer.load.js
│ │ │ ├── detect/ # usage.detect.js, mode.detect.js
│ │ │ ├── missing/ # Fallback handling
│ │ │ └── integration/fraud/
│ │ ├── survey/
│ │ │ ├── index.js # IIFE singleton
│ │ │ └── _event.survey.js
│ │ ├── gdpr/
│ │ │ └── index.js # IIFE singleton
│ │ └── screenshot/
│ │ └── index.js # Class-based
│ ├── helpers/
│ │ ├── recorder.helper.js # Encoding, event batching, ping URLs
│ │ ├── survey.helper.js # HTML generation
│ │ ├── consent.helper.js # Consent storage/checks
│ │ ├── session.helper.js # Session management
│ │ ├── key.helper.js # UUID/key generation
│ │ ├── event.helper.js # Custom event dispatching
│ │ └── common.helper.js # DOM utils, storage
│ ├── constants/
│ │ ├── host.constant.js # API/ping server URLs
│ │ ├── recorder.constant.js # Event types, selectors, MSR_* keys
│ │ ├── time.constant.js # Timeouts (30m, 12h)
│ │ └── survey.constant.js # Survey templates
│ ├── api-client/ # API integrations
│ └── rrweb/ # Customized rrweb fork
├── server/
│ └── index.js # Koa static server (port 2003, dev only)
├── webpack.config.js # Build config
├── package.json # Root scripts
└── pnpm-workspace.yaml # Monorepo (extensions/web-pixel has own package.json)
Webpack entries → assets:
scripts/modules/recorder/index.js → extensions/theme-app-extension/assets/recorder.msr.js
scripts/modules/survey/index.js → extensions/theme-app-extension/assets/survey.msr.js
scripts/modules/gdpr/index.js → extensions/theme-app-extension/assets/gdpr.msr.js
scripts/modules/screenshot/index.js → extensions/theme-app-extension/assets/screenshot.msr.js
Build commands:
npm run build-msr # Webpack: compile JS → assets/
npm run build-watch # Webpack watch mode
npm run build # Shopify CLI build
npm run dev # Shopify CLI dev (watch + hot reload)
npm run deploy # Deploy to Shopify
npm run start # Koa static server on port 2003
Build output is committed to git — .msr.js files in assets/ are the deployed artifacts.
extensions/theme-app-extension/blocks/mida_recorder.liquid is the entry point injected by Shopify into theme templates.
Conditional feature injection:
{% if app.metafields.module.srStatus == 'enable' %}
{% unless content_for_header contains 'Shopify.designMode' %}
<script src="{{ 'recorder.msr.js' | asset_url }}" defer="defer"></script>
{% endunless %}
{% endif %}
Global variables passed to JS:
<script>
window.msrPageTitle = {{ page.title | json }};
window.msrCart = {{ cart | json }};
window.msrCustomer = {{ customer | json }};
window.msrShopData = { template: {{ request.page_type | json }}, url: {{ canonical_url | json }} };
window.msrSetting = {{ shop.metafields.mida.setting | json }};
window.msrData = { proxy: {{ shop.metafields.mida.proxy | json }} };
window.msrQuota = {{ shop.metafields.mida.quota | json }};
</script>
Pattern: Read window.msr* variables in JS modules — never make API calls to get shop config.
// scripts/modules/recorder/index.js
const initialRecorder = (function() {
const recordState = {
block: false,
events: [],
sessionKey: null,
visitorKey: null,
eventCheck: {},
// ...
};
const init = () => {
// Read from window.msrShopData, window.msrSetting, etc.
// Check consent
// Start rrweb recording
};
const getState = () => ({ ...recordState });
init();
return { getState };
})();
window.msrRecordState = initialRecorder.getState();
Patterns:
getState() returns a copy via spreadwindow.msr[Name]State exposes state globallyrecordState.block = true stops all further processing// Named function exports (not default)
export const generateSessionKey = () => {
return 'sess_' + Math.random().toString(36).substr(2, 9);
};
export const buildPingUrl = (proxy, params) => {
const base = proxy === '2' ? MIDA_PING_URL_2 : MIDA_PING_URL;
return `${base}/sessions/tracker?${new URLSearchParams(params).toString()}`;
};
// constants/recorder.constant.js
export const MSR_SESSION_KEY = 'msr_session_key';
export const MSR_EVENT = {
QUICK_CHECKOUT: 'msr_quick_checkout',
ADD_TO_CART: 'msr_add_to_cart',
};
// constants/host.constant.js
export const MIDA_API_URL = process.env.MIDA_API_URL;
export const MIDA_PING_URL = process.env.MIDA_PING_URL;
All constants: MSR_ prefix. No magic strings in module code.
.Mida-Survey--root /* root container */
.Mida-Survey__Card /* block element */
.Mida-Survey__Button--send /* modifier */
.Mida-GDPR--root /* another module */
Z-index conventions:
z-index: 999999z-index: 99999999// Use custom events for cross-module communication
import { MSR_EVENT } from '../constants/recorder.constant.js';
document.dispatchEvent(new Event(MSR_EVENT.ADD_TO_CART));
// Listen:
document.addEventListener(MSR_EVENT.ADD_TO_CART, () => { ... });
// Event bubbling for DOM interactions:
document.addEventListener('click', (event) => {
if (event.target.matches('.checkout-button')) {
// handle
}
}, true);
// Prevent duplicate listeners:
if (!recordState.eventCheck['addToCart']) {
document.addEventListener('click', handler);
recordState.eventCheck['addToCart'] = true;
}
// Batch events in state.events array before sending
recordState.events.push({ type, timestamp, data });
// Send with keepalive on page unload
fetch(pingUrl, {
method: 'POST',
body: buildBody(JSON.stringify({ events: recordState.events })),
keepalive: true, // survives page navigation
});
// Split large payloads into 32KB chunks
const chunks = encodeSplit(JSON.stringify(bigPayload));
for (const chunk of chunks) await sendChunk(chunk);
extensions/web-pixel/src/index.js uses Shopify's web pixel API:
import { register } from '@shopify/web-pixels-extension';
register(({ analytics, browser, settings }) => {
analytics.subscribe('product_viewed', (event) => {
const { domain, proxy } = settings; // From shopify.extension.toml schema
// Track event
});
analytics.subscribe('checkout_started', (event) => { ... });
analytics.subscribe('payment_info_submitted', (event) => { ... });
analytics.subscribe('checkout_completed', (event) => { ... });
});
Settings (shopify.extension.toml): domain and proxy are passed from Shopify admin.
Runtime: strict mode — limited browser APIs via browser SDK object.
// Import custom fork (not npm package)
import rrwebRecord from '../rrweb/record/index.js';
window.rrwebRecord = rrwebRecord; // Expose globally
rrwebRecord({
emit(event) {
recordState.events.push(event);
},
blockClass: 'msr-block',
ignoreClass: 'msr-ignore',
sampling: { mousemove: 50 },
});
// webpack.config.js
export default {
mode: 'production',
target: ['web', 'es5'], // Must transpile to ES5
entry: { recorder: '...', survey: '...', gdpr: '...', screenshot: '...' },
output: {
filename: '[name].msr.js',
path: resolve(__dirname, 'extensions/theme-app-extension/assets'),
},
plugins: [new Dotenv()], // Injects MIDA_API_URL, MIDA_PING_URL, etc.
};
Environment variables injected by Dotenv plugin: MIDA_API_URL, MIDA_PING_URL, MIDA_HM_URL, MIDA_EXTERNAL_URL, and their _2 variants.
env: { browser: true, node: true, es2021: true }npm run lint && npm run formatwindow.msr* globals in .eslintrc.cjs as readonlyscripts/modules/<name>/index.js with IIFE patternwindow.msr<Name>State = initialModule.getState()webpack.config.jsscripts/helpers/<name>.helper.jsscripts/constants/<name>.constant.js<script> tag in mida_recorder.liquidMSR_EVENT.NEW_EVENT = 'msr_new_event' in recorder.constant.jsscripts/modules/recorder/load/event.load.jsdocument.dispatchEvent(new Event(MSR_EVENT.NEW_EVENT)).msr.js files in assets/ directly — they are build outputrecordState.block check in event handlersif (!checkConsentResult) returnkeepalive: true for fetch calls during page unload.Mida- to avoid theme conflictsMSR_ prefix for constants — no magic strings in module codenpm run build-msr before deployingnpm run build-msr # Build and check for webpack errors
npm run lint # ESLint check
npx claudepluginhub quyensatoru/mida-md --plugin mida-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.