@officexapp/catalogs-cli 0.4.12 → 0.4.13

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/index.js CHANGED
@@ -860,6 +860,11 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
860
860
  const schemaJson = JSON.stringify(schema).replace(/<\/script/gi, "<\\/script");
861
861
  const themeColor = schema.settings?.theme?.primary_color || "#6366f1";
862
862
  const stripeEnabled = devConfig?.stripeEnabled ?? false;
863
+ const prodUrl = devConfig?.prodUrl || "";
864
+ const schemaVersion = schema.schema_version || "1.0";
865
+ const pageIds = Object.keys(schema.pages || {});
866
+ const routingEdges = schema.routing?.edges || [];
867
+ const routingEntry = schema.routing?.entry || pageIds[0] || "";
863
868
  let validationHtml = "";
864
869
  if (validation && (validation.errors.length > 0 || validation.warnings.length > 0)) {
865
870
  const items = [
@@ -875,6 +880,18 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
875
880
  ${items}
876
881
  </div>`;
877
882
  }
883
+ const pagesGraphJson = JSON.stringify({
884
+ pages: pageIds.map((id) => ({
885
+ id,
886
+ title: schema.pages[id]?.title || schema.pages[id]?.settings?.title || id
887
+ })),
888
+ edges: routingEdges.map((e) => ({
889
+ from: e.from,
890
+ to: e.to,
891
+ label: e.conditions ? "conditional" : e.is_default ? "default" : ""
892
+ })),
893
+ entry: routingEntry
894
+ }).replace(/<\/script/gi, "<\\/script");
878
895
  return `<!DOCTYPE html>
879
896
  <html lang="en">
880
897
  <head>
@@ -893,34 +910,125 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
893
910
  "react-dom": "https://esm.sh/react-dom@18.3.1",
894
911
  "react-dom/client": "https://esm.sh/react-dom@18.3.1/client",
895
912
  "react/jsx-runtime": "https://esm.sh/react@18.3.1/jsx-runtime",
896
- "hls.js": "https://esm.sh/hls.js@1.5.17"
913
+ "hls.js": "https://esm.sh/hls.js@1.5.17",
914
+ "tldraw": "https://esm.sh/tldraw@2.4.1?external=react,react-dom",
915
+ "@tldraw/tldraw": "https://esm.sh/tldraw@2.4.1?external=react,react-dom"
897
916
  }
898
917
  }
899
918
  </script>
919
+ <link rel="stylesheet" href="https://esm.sh/tldraw@2.4.1/tldraw.css" />
900
920
  <style>
901
- /* Dev banner */
902
- #__dev_banner {
921
+ /* Dev toolbar \u2014 expanded */
922
+ #__dev_toolbar {
903
923
  position: fixed; top: 0; left: 0; right: 0; z-index: 10000;
904
924
  background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
905
925
  color: #c7d2fe; font-family: system-ui, sans-serif;
906
- font-size: 12px; padding: 6px 16px;
907
- display: flex; align-items: center; justify-content: space-between;
926
+ font-size: 12px; padding: 0 12px; height: 32px;
927
+ display: flex; align-items: center; gap: 6px;
908
928
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
909
929
  }
910
- #__dev_banner a { color: #a5b4fc; text-decoration: none; }
911
- #__dev_banner a:hover { text-decoration: underline; }
912
- body { padding-top: 32px; }
930
+ #__dev_toolbar a { color: #a5b4fc; text-decoration: none; }
931
+ #__dev_toolbar a:hover { text-decoration: underline; }
932
+ #__dev_toolbar .tb-sep { width: 1px; height: 16px; background: rgba(165,180,252,0.2); margin: 0 4px; }
933
+ #__dev_toolbar .tb-btn {
934
+ background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1);
935
+ color: #c7d2fe; border-radius: 6px; padding: 2px 8px; cursor: pointer;
936
+ font-size: 11px; font-family: inherit; display: flex; align-items: center; gap: 4px;
937
+ transition: background 0.15s;
938
+ }
939
+ #__dev_toolbar .tb-btn:hover { background: rgba(255,255,255,0.15); }
940
+ #__dev_toolbar .tb-btn.active { background: rgba(99,102,241,0.4); border-color: #6366f1; }
941
+ #__dev_toolbar .tb-right { margin-left: auto; display: flex; align-items: center; gap: 6px; }
942
+ body.toolbar-expanded { padding-top: 32px; }
943
+
944
+ /* Minimized pill */
945
+ #__dev_pill {
946
+ position: fixed; top: 8px; right: 8px; z-index: 10000;
947
+ background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
948
+ color: #e0e7ff; font-family: system-ui, sans-serif;
949
+ font-size: 11px; padding: 4px 10px; border-radius: 20px;
950
+ cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.3);
951
+ display: none; align-items: center; gap: 6px;
952
+ border: 1px solid rgba(99,102,241,0.3);
953
+ }
954
+ #__dev_pill:hover { background: linear-gradient(135deg, #312e81 0%, #4338ca 100%); }
955
+
956
+ /* Pages graph panel */
957
+ #__dev_pages_panel {
958
+ position: fixed; top: 32px; left: 0; right: 0; z-index: 9999;
959
+ height: 0; overflow: hidden; transition: height 0.25s ease;
960
+ background: #0f0e26; border-bottom: 1px solid rgba(99,102,241,0.2);
961
+ }
962
+ #__dev_pages_panel.open { height: 40vh; }
963
+ #__dev_pages_canvas {
964
+ width: 100%; height: 100%; position: relative;
965
+ overflow: auto; padding: 20px;
966
+ }
967
+ .pg-node {
968
+ position: absolute; background: #1e1b4b; border: 2px solid #4338ca;
969
+ border-radius: 10px; padding: 8px 14px; color: #e0e7ff;
970
+ font-size: 12px; font-family: system-ui, sans-serif;
971
+ cursor: pointer; white-space: nowrap; min-width: 80px; text-align: center;
972
+ transition: border-color 0.15s, box-shadow 0.15s;
973
+ }
974
+ .pg-node:hover { border-color: #818cf8; box-shadow: 0 0 12px rgba(99,102,241,0.3); }
975
+ .pg-node.entry { border-color: #4ade80; }
976
+ .pg-node.current { border-color: ${themeColor}; box-shadow: 0 0 16px ${themeColor}44; }
977
+ .pg-edge-label {
978
+ position: absolute; color: #818cf8; font-size: 10px;
979
+ font-family: system-ui, sans-serif; pointer-events: none;
980
+ }
981
+
982
+ /* Debug badge colors */
983
+ .debug-none { color: #6b7280; }
984
+ .debug-slim { color: #fbbf24; }
985
+ .debug-verbose { color: #ef4444; }
913
986
  </style>
914
987
  </head>
915
- <body>
916
- <!-- Dev Banner -->
917
- <div id="__dev_banner">
918
- <span><strong style="color:#e0e7ff">${schema.slug || "catalog"}</strong> &mdash; local preview</span>
919
- <span>
920
- ${stripeEnabled ? '<span style="color:#4ade80">&#9679;</span> Stripe' : '<span style="color:#fbbf24">&#9675;</span> Stripe stubbed'}
921
- &nbsp;&middot;&nbsp;
988
+ <body class="toolbar-expanded">
989
+ <!-- Dev Toolbar (expanded) -->
990
+ <div id="__dev_toolbar">
991
+ <strong style="color:#e0e7ff">${schema.slug || "catalog"}</strong>
992
+ <span style="color:#818cf8;font-size:10px">v${schemaVersion}</span>
993
+
994
+ <div class="tb-sep"></div>
995
+
996
+ <button class="tb-btn" id="__tb_pages" title="Page graph">
997
+ <span style="font-size:13px">&#128196;</span> Pages
998
+ </button>
999
+
1000
+ <button class="tb-btn" id="__tb_inspect" title="Toggle element inspector">
1001
+ <span style="font-size:13px">&#127919;</span> Inspect
1002
+ </button>
1003
+
1004
+ <button class="tb-btn" id="__tb_debug" title="Cycle debug mode: none / slim / verbose">
1005
+ <span style="font-size:13px">&#128027;</span> Debug: <span id="__tb_debug_label" class="debug-none">none</span>
1006
+ </button>
1007
+
1008
+ <div class="tb-right">
1009
+ ${stripeEnabled ? '<span style="color:#4ade80">&#9679;</span> <span>Stripe</span>' : '<span style="color:#fbbf24">&#9675;</span> <span>Stripe stubbed</span>'}
1010
+
1011
+ <div class="tb-sep"></div>
922
1012
  <a href="/__dev_events" target="_blank">Events</a>
923
- </span>
1013
+
1014
+ <div class="tb-sep"></div>
1015
+ ${prodUrl ? `<a href="${prodUrl}" target="_blank" title="Open production URL">&#8599; Prod</a>` : '<span style="color:#6b7280;cursor:default" title="Push catalog to see prod URL">&#8599; Prod</span>'}
1016
+
1017
+ <div class="tb-sep"></div>
1018
+ <button class="tb-btn" id="__tb_minimize" title="Minimize toolbar" style="padding:2px 6px;font-size:14px;line-height:1">&mdash;</button>
1019
+ </div>
1020
+ </div>
1021
+
1022
+ <!-- Minimized pill -->
1023
+ <div id="__dev_pill">
1024
+ <strong>${schema.slug || "catalog"}</strong>
1025
+ <span style="font-size:13px">&#9650;</span>
1026
+ </div>
1027
+
1028
+ <!-- Pages graph panel -->
1029
+ <div id="__dev_pages_panel">
1030
+ <svg id="__dev_pages_svg" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1"></svg>
1031
+ <div id="__dev_pages_canvas"></div>
924
1032
  </div>
925
1033
 
926
1034
  <!-- React mount point -->
@@ -928,7 +1036,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
928
1036
 
929
1037
  ${validationHtml}
930
1038
 
931
- <!-- Mount CatalogRenderer from renderer bundle -->
1039
+ <!-- Dev toolbar logic + CatalogRenderer mount -->
932
1040
  <script type="module">
933
1041
  import React from "react";
934
1042
  import { createRoot } from "react-dom/client";
@@ -936,11 +1044,232 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
936
1044
 
937
1045
  const schema = ${schemaJson};
938
1046
  const port = ${port};
1047
+ const pagesGraph = ${pagesGraphJson};
1048
+
1049
+ // ============ Toolbar State ============
1050
+
1051
+ // --- Minimize/Expand ---
1052
+ const toolbar = document.getElementById("__dev_toolbar");
1053
+ const pill = document.getElementById("__dev_pill");
1054
+ const pagesPanel = document.getElementById("__dev_pages_panel");
1055
+ let minimized = sessionStorage.getItem("__dev_toolbar_minimized") === "true";
1056
+
1057
+ function applyMinimized() {
1058
+ if (minimized) {
1059
+ toolbar.style.display = "none";
1060
+ pill.style.display = "flex";
1061
+ document.body.classList.remove("toolbar-expanded");
1062
+ if (pagesPanel.classList.contains("open")) pagesPanel.classList.remove("open");
1063
+ } else {
1064
+ toolbar.style.display = "flex";
1065
+ pill.style.display = "none";
1066
+ document.body.classList.add("toolbar-expanded");
1067
+ }
1068
+ sessionStorage.setItem("__dev_toolbar_minimized", String(minimized));
1069
+ }
1070
+ applyMinimized();
1071
+
1072
+ document.getElementById("__tb_minimize").addEventListener("click", () => {
1073
+ minimized = true; applyMinimized();
1074
+ });
1075
+ pill.addEventListener("click", () => {
1076
+ minimized = false; applyMinimized();
1077
+ });
1078
+
1079
+ // --- Debug Mode Toggle ---
1080
+ const debugModes = ["none", "slim", "verbose"];
1081
+ const debugColors = { none: "debug-none", slim: "debug-slim", verbose: "debug-verbose" };
1082
+ let debugIndex = debugModes.indexOf(new URL(location.href).searchParams.get("debug_mode") || "none");
1083
+ if (debugIndex < 0) debugIndex = 0;
1084
+ const debugLabel = document.getElementById("__tb_debug_label");
1085
+ const debugBtn = document.getElementById("__tb_debug");
1086
+
1087
+ function applyDebug() {
1088
+ const mode = debugModes[debugIndex];
1089
+ debugLabel.textContent = mode;
1090
+ debugLabel.className = debugColors[mode];
1091
+ debugBtn.classList.toggle("active", mode !== "none");
1092
+ const u = new URL(location.href);
1093
+ if (mode === "none") u.searchParams.delete("debug_mode");
1094
+ else u.searchParams.set("debug_mode", mode);
1095
+ history.replaceState(null, "", u.toString());
1096
+ }
1097
+ applyDebug();
1098
+
1099
+ debugBtn.addEventListener("click", () => {
1100
+ debugIndex = (debugIndex + 1) % debugModes.length;
1101
+ applyDebug();
1102
+ });
1103
+
1104
+ // --- Element Inspector Toggle ---
1105
+ let inspectorActive = false;
1106
+ const inspectBtn = document.getElementById("__tb_inspect");
1107
+
1108
+ inspectBtn.addEventListener("click", () => {
1109
+ inspectorActive = !inspectorActive;
1110
+ window.__devInspectorActive = inspectorActive;
1111
+ inspectBtn.classList.toggle("active", inspectorActive);
1112
+ document.body.style.cursor = inspectorActive ? "crosshair" : "";
1113
+ window.dispatchEvent(new CustomEvent("dev:inspector-toggle"));
1114
+ });
1115
+
1116
+ // --- Pages Graph Panel ---
1117
+ let pagesOpen = false;
1118
+ const pagesBtn = document.getElementById("__tb_pages");
1119
+ const pagesCanvas = document.getElementById("__dev_pages_canvas");
1120
+ const pagesSvg = document.getElementById("__dev_pages_svg");
1121
+ let currentPageId = pagesGraph.entry;
1122
+
1123
+ function buildPagesGraph() {
1124
+ pagesCanvas.innerHTML = "";
1125
+ pagesSvg.innerHTML = "";
1126
+ if (pagesGraph.pages.length === 0) {
1127
+ pagesCanvas.innerHTML = '<div style="color:#818cf8;padding:20px;font-size:13px">No pages defined</div>';
1128
+ return;
1129
+ }
1130
+
1131
+ // BFS layout: levels from entry
1132
+ const adj = {};
1133
+ pagesGraph.pages.forEach(p => { adj[p.id] = []; });
1134
+ pagesGraph.edges.forEach(e => {
1135
+ if (e.from && e.to && adj[e.from]) adj[e.from].push(e.to);
1136
+ });
1137
+
1138
+ const levels = {};
1139
+ const visited = new Set();
1140
+ const queue = [pagesGraph.entry];
1141
+ visited.add(pagesGraph.entry);
1142
+ levels[pagesGraph.entry] = 0;
1143
+
1144
+ while (queue.length > 0) {
1145
+ const node = queue.shift();
1146
+ for (const child of (adj[node] || [])) {
1147
+ if (!visited.has(child)) {
1148
+ visited.add(child);
1149
+ levels[child] = (levels[node] || 0) + 1;
1150
+ queue.push(child);
1151
+ }
1152
+ }
1153
+ }
1154
+
1155
+ // Place unvisited nodes at max level + 1
1156
+ let maxLevel = Math.max(0, ...Object.values(levels));
1157
+ pagesGraph.pages.forEach(p => {
1158
+ if (levels[p.id] === undefined) {
1159
+ levels[p.id] = maxLevel + 1;
1160
+ }
1161
+ });
1162
+
1163
+ // Group by level for positioning
1164
+ const byLevel = {};
1165
+ pagesGraph.pages.forEach(p => {
1166
+ const l = levels[p.id];
1167
+ if (!byLevel[l]) byLevel[l] = [];
1168
+ byLevel[l].push(p);
1169
+ });
1170
+
1171
+ const nodeWidth = 120;
1172
+ const nodeHeight = 36;
1173
+ const levelGapY = 80;
1174
+ const nodeGapX = 160;
1175
+ const positions = {};
1176
+
1177
+ Object.keys(byLevel).sort((a, b) => a - b).forEach(level => {
1178
+ const nodes = byLevel[level];
1179
+ const totalWidth = nodes.length * nodeGapX;
1180
+ const startX = (pagesCanvas.offsetWidth || 600) / 2 - totalWidth / 2 + nodeGapX / 2 - nodeWidth / 2;
1181
+ nodes.forEach((node, i) => {
1182
+ const x = startX + i * nodeGapX;
1183
+ const y = 20 + level * levelGapY;
1184
+ positions[node.id] = { x, y };
1185
+
1186
+ const div = document.createElement("div");
1187
+ div.className = "pg-node" +
1188
+ (node.id === pagesGraph.entry ? " entry" : "") +
1189
+ (node.id === currentPageId ? " current" : "");
1190
+ div.style.left = x + "px";
1191
+ div.style.top = y + "px";
1192
+ div.textContent = node.title;
1193
+ div.title = "Navigate to: " + node.id;
1194
+ div.addEventListener("click", () => {
1195
+ window.dispatchEvent(new CustomEvent("dev:navigate", { detail: { pageId: node.id } }));
1196
+ currentPageId = node.id;
1197
+ buildPagesGraph();
1198
+ });
1199
+ pagesCanvas.appendChild(div);
1200
+ });
1201
+ });
1202
+
1203
+ // Draw edges as SVG arrows
1204
+ pagesGraph.edges.forEach(edge => {
1205
+ if (!positions[edge.from] || !positions[edge.to]) return;
1206
+ const from = positions[edge.from];
1207
+ const to = positions[edge.to];
1208
+ const x1 = from.x + nodeWidth / 2;
1209
+ const y1 = from.y + nodeHeight;
1210
+ const x2 = to.x + nodeWidth / 2;
1211
+ const y2 = to.y;
1212
+
1213
+ const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
1214
+ line.setAttribute("x1", x1);
1215
+ line.setAttribute("y1", y1);
1216
+ line.setAttribute("x2", x2);
1217
+ line.setAttribute("y2", y2);
1218
+ line.setAttribute("stroke", "#4338ca");
1219
+ line.setAttribute("stroke-width", "2");
1220
+ line.setAttribute("marker-end", "url(#arrowhead)");
1221
+ pagesSvg.appendChild(line);
1222
+
1223
+ // Edge label
1224
+ if (edge.label) {
1225
+ const lbl = document.createElement("div");
1226
+ lbl.className = "pg-edge-label";
1227
+ lbl.style.left = ((x1 + x2) / 2 - 20) + "px";
1228
+ lbl.style.top = ((y1 + y2) / 2 - 8) + "px";
1229
+ lbl.textContent = edge.label;
1230
+ pagesCanvas.appendChild(lbl);
1231
+ }
1232
+ });
1233
+
1234
+ // SVG arrowhead marker
1235
+ if (!pagesSvg.querySelector("defs")) {
1236
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
1237
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
1238
+ marker.setAttribute("id", "arrowhead");
1239
+ marker.setAttribute("markerWidth", "10");
1240
+ marker.setAttribute("markerHeight", "7");
1241
+ marker.setAttribute("refX", "9");
1242
+ marker.setAttribute("refY", "3.5");
1243
+ marker.setAttribute("orient", "auto");
1244
+ const poly = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
1245
+ poly.setAttribute("points", "0 0, 10 3.5, 0 7");
1246
+ poly.setAttribute("fill", "#4338ca");
1247
+ marker.appendChild(poly);
1248
+ defs.appendChild(marker);
1249
+ pagesSvg.appendChild(defs);
1250
+ }
1251
+ }
1252
+
1253
+ pagesBtn.addEventListener("click", () => {
1254
+ pagesOpen = !pagesOpen;
1255
+ pagesPanel.classList.toggle("open", pagesOpen);
1256
+ pagesBtn.classList.toggle("active", pagesOpen);
1257
+ if (pagesOpen) {
1258
+ // Slight delay so the panel has dimensions for layout
1259
+ requestAnimationFrame(() => buildPagesGraph());
1260
+ }
1261
+ });
1262
+
1263
+ // Update current page highlight when catalog navigates
1264
+ window.addEventListener("dev:page-changed", (e) => {
1265
+ currentPageId = e.detail?.pageId || currentPageId;
1266
+ if (pagesOpen) buildPagesGraph();
1267
+ });
1268
+
1269
+ // ============ Dev Services ============
939
1270
 
940
- // --- Dev Services ---
941
1271
  const devTracker = {
942
1272
  track(payload) {
943
- // Post to dev event endpoint
944
1273
  fetch("/__dev_event", {
945
1274
  method: "POST",
946
1275
  headers: { "Content-Type": "application/json" },
@@ -960,6 +1289,8 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
960
1289
  }).catch(() => {});
961
1290
  },
962
1291
  onPageChange(pageId) {
1292
+ currentPageId = pageId;
1293
+ window.dispatchEvent(new CustomEvent("dev:page-changed", { detail: { pageId } }));
963
1294
  fetch("/__dev_event", {
964
1295
  method: "POST",
965
1296
  headers: { "Content-Type": "application/json" },
@@ -968,7 +1299,8 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
968
1299
  },
969
1300
  };
970
1301
 
971
- // --- Mount ---
1302
+ // ============ Mount CatalogRenderer ============
1303
+
972
1304
  const root = createRoot(document.getElementById("root"));
973
1305
  root.render(
974
1306
  React.createElement(CatalogRenderer, {
@@ -979,6 +1311,20 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
979
1311
  })
980
1312
  );
981
1313
 
1314
+ // Listen for page navigation from graph panel
1315
+ window.addEventListener("dev:navigate", (e) => {
1316
+ const pageId = e.detail?.pageId;
1317
+ if (pageId) {
1318
+ // Try multiple approaches to navigate
1319
+ if (typeof window.__dev_navigateTo === "function") {
1320
+ window.__dev_navigateTo(pageId);
1321
+ } else {
1322
+ // Use URL hash as fallback
1323
+ window.location.hash = pageId;
1324
+ }
1325
+ }
1326
+ });
1327
+
982
1328
  // --- Auto-reload via SSE ---
983
1329
  const sse = new EventSource("/__dev_sse");
984
1330
  sse.onmessage = (e) => {
@@ -987,7 +1333,6 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
987
1333
 
988
1334
  // --- Checkout redirect override for local dev ---
989
1335
  ${stripeEnabled ? `
990
- // Override checkout endpoint to use local dev server
991
1336
  const _origFetch = window.fetch;
992
1337
  window.fetch = function(url, opts) {
993
1338
  if (typeof url === "string" && url.includes("/checkout/session")) {
@@ -996,7 +1341,6 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
996
1341
  return _origFetch.apply(this, arguments);
997
1342
  };
998
1343
  ` : `
999
- // Stub checkout \u2014 no Stripe key
1000
1344
  const _origFetch = window.fetch;
1001
1345
  window.fetch = function(url, opts) {
1002
1346
  if (typeof url === "string" && url.includes("/checkout/session")) {
@@ -1034,7 +1378,22 @@ async function catalogDev(file, opts) {
1034
1378
  }
1035
1379
  }
1036
1380
  const stripeEnabled = stripeSecretKey.length > 0;
1037
- const devConfig = { stripeEnabled, port };
1381
+ let prodUrl = "";
1382
+ const cliConfig = getConfig();
1383
+ if (cliConfig.token) {
1384
+ try {
1385
+ const listRes = await fetch(`${cliConfig.api_url}/api/v1/catalogs`, {
1386
+ headers: { Authorization: `Bearer ${cliConfig.token}` }
1387
+ });
1388
+ if (listRes.ok) {
1389
+ const listData = await listRes.json();
1390
+ const catalogs = listData.data || [];
1391
+ globalThis.__dev_catalogs = catalogs;
1392
+ }
1393
+ } catch {
1394
+ }
1395
+ }
1396
+ const devConfig = { stripeEnabled, port, prodUrl };
1038
1397
  const spinner = ora5("Loading catalog schema...").start();
1039
1398
  let schema;
1040
1399
  try {
@@ -1054,9 +1413,37 @@ async function catalogDev(file, opts) {
1054
1413
  let validation = deepValidateCatalog(schema);
1055
1414
  const localBaseUrl = `http://localhost:${port}/assets`;
1056
1415
  schema = resolveLocalAssets(schema, catalogDir, localBaseUrl);
1416
+ if (globalThis.__dev_catalogs && schema.slug) {
1417
+ const catalogs = globalThis.__dev_catalogs;
1418
+ const catalog2 = catalogs.find((c) => c.slug === schema.slug);
1419
+ if (catalog2) {
1420
+ if (catalog2.url) {
1421
+ devConfig.prodUrl = catalog2.url;
1422
+ } else {
1423
+ try {
1424
+ const meRes = await fetch(`${cliConfig.api_url}/api/v1/me`, {
1425
+ headers: { Authorization: `Bearer ${cliConfig.token}` }
1426
+ });
1427
+ if (meRes.ok) {
1428
+ const meData = await meRes.json();
1429
+ const subdomain = meData.data?.subdomain || meData.data?.app_slug;
1430
+ if (subdomain) {
1431
+ devConfig.prodUrl = `https://${subdomain}.catalogkit.cc/${schema.slug}`;
1432
+ }
1433
+ }
1434
+ } catch {
1435
+ }
1436
+ }
1437
+ if (!devConfig.prodUrl && catalog2.catalog_id) {
1438
+ devConfig.prodUrl = `https://catalogkit.cc/c/${catalog2.catalog_id}`;
1439
+ }
1440
+ }
1441
+ delete globalThis.__dev_catalogs;
1442
+ }
1057
1443
  spinner.succeed(`Loaded: ${schema.slug || file}`);
1058
1444
  console.log(` Pages: ${Object.keys(schema.pages || {}).length}`);
1059
1445
  console.log(` Entry: ${schema.routing?.entry || "first page"}`);
1446
+ if (devConfig.prodUrl) console.log(` Prod: ${devConfig.prodUrl}`);
1060
1447
  console.log();
1061
1448
  const sseClients = /* @__PURE__ */ new Set();
1062
1449
  const eventSseClients = /* @__PURE__ */ new Set();
@@ -1118,6 +1505,18 @@ async function catalogDev(file, opts) {
1118
1505
  res.end(JSON.stringify(eventLog.slice(-limit)));
1119
1506
  return;
1120
1507
  }
1508
+ if (url.pathname === "/__dev_meta" && req.method === "GET") {
1509
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
1510
+ res.end(JSON.stringify({
1511
+ slug: schema.slug || "",
1512
+ schema_version: schema.schema_version || "1.0",
1513
+ prod_url: devConfig.prodUrl || null,
1514
+ pages_count: Object.keys(schema.pages || {}).length,
1515
+ entry: schema.routing?.entry || null,
1516
+ stripe_enabled: stripeEnabled
1517
+ }));
1518
+ return;
1519
+ }
1121
1520
  if (url.pathname === "/__dev_event" && req.method === "POST") {
1122
1521
  let body = "";
1123
1522
  req.on("data", (chunk) => {
@@ -5298,22 +5298,33 @@ var ElementInspector = ({ context }) => {
5298
5298
  const onKeyUp = (e) => {
5299
5299
  keysDown.delete(e.key);
5300
5300
  if (!e.shiftKey || !e.altKey) {
5301
- setActive(false);
5302
- setTooltip(null);
5301
+ if (!window.__devInspectorActive) {
5302
+ setActive(false);
5303
+ setTooltip(null);
5304
+ }
5303
5305
  }
5304
5306
  };
5305
5307
  const onBlur = () => {
5306
5308
  keysDown.clear();
5307
- setActive(false);
5308
- setTooltip(null);
5309
+ if (!window.__devInspectorActive) {
5310
+ setActive(false);
5311
+ setTooltip(null);
5312
+ }
5313
+ };
5314
+ const onDevToggle = () => {
5315
+ const isOn = !!window.__devInspectorActive;
5316
+ setActive(isOn);
5317
+ if (!isOn) setTooltip(null);
5309
5318
  };
5310
5319
  window.addEventListener("keydown", onKeyDown);
5311
5320
  window.addEventListener("keyup", onKeyUp);
5312
5321
  window.addEventListener("blur", onBlur);
5322
+ window.addEventListener("dev:inspector-toggle", onDevToggle);
5313
5323
  return () => {
5314
5324
  window.removeEventListener("keydown", onKeyDown);
5315
5325
  window.removeEventListener("keyup", onKeyUp);
5316
5326
  window.removeEventListener("blur", onBlur);
5327
+ window.removeEventListener("dev:inspector-toggle", onDevToggle);
5317
5328
  };
5318
5329
  }, []);
5319
5330
  const onMouseMove = useCallback7(
@@ -6225,6 +6236,7 @@ var CatalogRenderer = ({ catalog: rawCatalog, variantSlug, userId, trackingEnabl
6225
6236
  const pageId = e.state?.pageId;
6226
6237
  const prevHistory = e.state?.history || [];
6227
6238
  if (pageId && catalog.pages[pageId]) {
6239
+ setShowCheckout(false);
6228
6240
  isNavigatingRef.current = true;
6229
6241
  setPageTransitioning(true);
6230
6242
  setCurrentPageIdRaw(pageId);
@@ -6695,14 +6707,11 @@ var CatalogRenderer = ({ catalog: rawCatalog, variantSlug, userId, trackingEnabl
6695
6707
  );
6696
6708
  const handleBack = useCallback8(() => {
6697
6709
  if (history.length > 0) {
6698
- const newHistory = history.slice(0, -1);
6699
6710
  const prevPageId = history[history.length - 1];
6700
6711
  track("page_back", { page_id: currentPageId, value: { from_page: currentPageId, to_page: prevPageId } });
6701
- setHistory(newHistory);
6702
- setCurrentPageId(prevPageId);
6703
- window.history.replaceState({ pageId: prevPageId, history: newHistory }, "");
6712
+ window.history.back();
6704
6713
  }
6705
- }, [history, setCurrentPageId, currentPageId, track]);
6714
+ }, [history, currentPageId, track]);
6706
6715
  useEffect8(() => {
6707
6716
  if (!submitted) {
6708
6717
  localStorage.setItem(saveKey, JSON.stringify({ formState, currentPageId, history }));