@the-portland-company/shell 0.61.1 → 0.63.0

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.
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { broadcastLogout, watchLogout } from './chunk-LL6LSJAH.js';
3
- import { clearBetaBannerDismissal, standardPolitogyNav, isNavItemActive, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SpecOverlayAction, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-G5DXKB3R.js';
3
+ import { clearBetaBannerDismissal, standardPolitogyNav, isNavItemActive, NavAppShell, NavChromeProvider, FuelAction, NavDisplayAction, TopBarWidgetSlot, SpecOverlayAction, SupportAction, NotificationsAction, useChromeMe, useThemePreference, useNavChromeFetcher, useChromeOrgs } from './chunk-2SF25TVU.js';
4
4
  import './chunk-U4RZPZET.js';
5
5
  import { NAV_DISPLAY_DEFAULT, resolveNavDisplay, isNavDisplayPreference, writeNavDisplayCookie, writeNavDisplayLocal, NAV_DISPLAY_STORAGE_KEY, readNavDisplayCookie } from './chunk-6UHJV2FH.js';
6
6
  import { resolveActiveOrg, isOrgId, writeActiveOrgCookie, ACTIVE_ORG_QUERY_PARAM, readActiveOrgCookie } from './chunk-W2HITMV5.js';
@@ -1288,36 +1288,44 @@ function currentChromeTheme() {
1288
1288
  }
1289
1289
  var SPEC_OVERLAY_ROOT_ATTR = "data-spec-overlay-root";
1290
1290
  async function captureMyView(status, orgId, orgName) {
1291
- const { domToBlob } = await import('modern-screenshot');
1291
+ const { domToCanvas } = await import('modern-screenshot');
1292
1292
  const w = window.innerWidth;
1293
1293
  const h = window.innerHeight;
1294
1294
  const theme = currentChromeTheme();
1295
1295
  const bg = getComputedStyle(document.body).backgroundColor || (theme === "dark" ? "#0b0b0c" : "#ffffff");
1296
- const blob = await domToBlob(document.body, {
1296
+ const full = await domToCanvas(document.body, {
1297
1297
  width: w,
1298
1298
  height: h,
1299
1299
  scale: 1,
1300
1300
  backgroundColor: bg,
1301
- type: "image/png",
1302
1301
  // Cross-origin images that refuse CORS render blank instead of failing.
1303
1302
  fetch: { requestInit: { mode: "cors", credentials: "omit" }, placeholderImage: "" },
1304
- filter: (node) => !(node instanceof Element && node.hasAttribute(SPEC_OVERLAY_ROOT_ATTR)),
1303
+ // Drop the Spec drawer and everything fixed (sidebar, toasts, popovers):
1304
+ // the design is the page body only, so chrome would only add noise.
1305
+ filter: (node) => !(node instanceof Element) || !node.hasAttribute(SPEC_OVERLAY_ROOT_ATTR) && getComputedStyle(node).position !== "fixed",
1305
1306
  style: { transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)` }
1306
1307
  });
1307
- if (!blob) throw new Error("page raster failed");
1308
- let crop = null;
1309
- const el = status.live_selector ? document.querySelector(status.live_selector) : null;
1308
+ const el = (status.live_selector ? document.querySelector(status.live_selector) : null) ?? document.querySelector("[data-shell-content]");
1309
+ let region = { x: 0, y: 0, width: w, height: h };
1310
1310
  if (el) {
1311
1311
  const r = el.getBoundingClientRect();
1312
1312
  const x = Math.max(0, Math.round(r.left));
1313
1313
  const y = Math.max(0, Math.round(r.top));
1314
- crop = { x, y, width: Math.min(w - x, Math.round(r.width)), height: Math.min(h - y, Math.round(r.height)) };
1314
+ region = { x, y, width: Math.min(w - x, Math.round(r.width)), height: Math.min(h - y, Math.round(r.height)) };
1315
1315
  }
1316
+ const out = document.createElement("canvas");
1317
+ out.width = region.width;
1318
+ out.height = region.height;
1319
+ const ctx = out.getContext("2d");
1320
+ if (!ctx) throw new Error("page raster failed");
1321
+ ctx.drawImage(full, region.x, region.y, region.width, region.height, 0, 0, region.width, region.height);
1322
+ const blob = await new Promise((resolve) => out.toBlob(resolve, "image/png"));
1323
+ if (!blob) throw new Error("page raster failed");
1316
1324
  const form = new FormData();
1317
1325
  form.append("image", blob, "live.png");
1318
1326
  form.append(
1319
1327
  "meta",
1320
- JSON.stringify({ url: window.location.href, theme, org_id: orgId ?? null, org_name: orgName ?? null, crop })
1328
+ JSON.stringify({ url: window.location.href, theme, org_id: orgId ?? null, org_name: orgName ?? null, crop: null })
1321
1329
  );
1322
1330
  const res = await fetch(status.capture_url ?? `${SPECS_ORIGIN}/api/overlay/capture`, {
1323
1331
  method: "POST",
@@ -1327,6 +1335,24 @@ async function captureMyView(status, orgId, orgName) {
1327
1335
  });
1328
1336
  if (!res.ok) throw new Error(`capture ${res.status}: ${(await res.text()).slice(0, 160)}`);
1329
1337
  }
1338
+ function SpecCaptureLoading() {
1339
+ return /* @__PURE__ */ jsxs("div", { "data-spec-capture-loading": "", role: "status", "aria-live": "polite", className: "flex-1 overflow-hidden px-4 py-4", children: [
1340
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-3 text-sm text-[color:var(--foreground)]", children: [
1341
+ /* @__PURE__ */ jsx(
1342
+ "span",
1343
+ {
1344
+ "aria-hidden": "true",
1345
+ className: "inline-block h-5 w-5 animate-spin rounded-full border-2 border-[color:var(--line)] border-t-[color:var(--color-accent)] motion-reduce:animate-none"
1346
+ }
1347
+ ),
1348
+ "Capturing your view and comparing\u2026"
1349
+ ] }),
1350
+ ["Live \u2014 your view", "Diff"].map((label) => /* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
1351
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-[color:var(--muted)]", children: label }),
1352
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "h-40 w-full animate-pulse rounded border border-[color:var(--line)] bg-[color:var(--panel)] motion-reduce:animate-none" })
1353
+ ] }, label))
1354
+ ] });
1355
+ }
1330
1356
  function specOverlayStatusPath() {
1331
1357
  return `/api/overlay/status?url=${encodeURIComponent(window.location.href)}&theme=${currentChromeTheme()}`;
1332
1358
  }
@@ -1336,6 +1362,7 @@ function SpecOverlayAction({ orgId, orgName } = {}) {
1336
1362
  const [open, setOpen] = useState(false);
1337
1363
  const [capture, setCapture] = useState({ state: "idle" });
1338
1364
  const [frameKey, setFrameKey] = useState(0);
1365
+ const [copied, setCopied] = useState("idle");
1339
1366
  const href = useLocationHref();
1340
1367
  useEffect(() => {
1341
1368
  if (access) return;
@@ -1489,16 +1516,49 @@ function SpecOverlayAction({ orgId, orgName } = {}) {
1489
1516
  onClick: () => void runCapture(status),
1490
1517
  className: "ml-auto rounded-md border border-[color:var(--line)] px-2 py-1 text-[color:var(--foreground)] hover:bg-[color:var(--panel)] disabled:opacity-60",
1491
1518
  title: "Your view is captured when the drawer opens. Recapture after changing the page.",
1492
- children: capture.state === "busy" ? "Capturing your view\u2026" : "Recapture"
1519
+ children: capture.state === "busy" ? "Capturing\u2026" : "Recapture"
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ jsx(
1523
+ "button",
1524
+ {
1525
+ type: "button",
1526
+ "data-spec-copy-prompt": "",
1527
+ disabled: capture.state === "busy" || !status.prompt,
1528
+ onClick: () => {
1529
+ const text = status.prompt;
1530
+ if (!text) return;
1531
+ navigator.clipboard.writeText(text).then(() => setCopied("copied")).catch(() => setCopied("failed")).finally(() => window.setTimeout(() => setCopied("idle"), 2e3));
1532
+ },
1533
+ className: "rounded-md border border-[color:var(--line)] px-2 py-1 text-[color:var(--foreground)] hover:bg-[color:var(--panel)] disabled:opacity-60",
1534
+ title: "Copy a repair prompt for an AI agent: spec, score, image links, top mismatched regions and instructions.",
1535
+ children: copied === "copied" ? "Copied \u2713" : copied === "failed" ? "Copy failed" : "Copy prompt"
1493
1536
  }
1494
1537
  )
1495
1538
  ] }),
1496
- capture.state === "busy" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-[color:var(--line)] px-4 py-2 text-xs text-[color:var(--muted)]", children: [
1497
- /* @__PURE__ */ jsx(ValueSkeleton, { className: "h-3 w-3 rounded-full" }),
1498
- "Capturing your view and comparing\u2026"
1499
- ] }) : null,
1539
+ status.live_size && status.design_size && capture.state !== "busy" ? /* @__PURE__ */ jsxs(
1540
+ "p",
1541
+ {
1542
+ "data-spec-sizes": "",
1543
+ className: [
1544
+ "border-b border-[color:var(--line)] px-4 py-1.5 text-xs",
1545
+ status.live_size.width === status.design_size.width && status.live_size.height === status.design_size.height ? "text-[color:var(--muted)]" : "font-semibold text-[color:var(--chrome-danger)]"
1546
+ ].join(" "),
1547
+ children: [
1548
+ "Live ",
1549
+ status.live_size.width,
1550
+ "\xD7",
1551
+ status.live_size.height,
1552
+ " \xB7 Design ",
1553
+ status.design_size.width,
1554
+ "\xD7",
1555
+ status.design_size.height,
1556
+ status.live_size.width === status.design_size.width && status.live_size.height === status.design_size.height ? " \xB7 sizes match" : " \xB7 SIZE MISMATCH"
1557
+ ]
1558
+ }
1559
+ ) : null,
1500
1560
  capture.state === "error" ? /* @__PURE__ */ jsx("p", { className: "border-b border-[color:var(--line)] px-4 py-2 text-xs text-[color:var(--chrome-danger)]", children: capture.message }) : null,
1501
- /* @__PURE__ */ jsx(
1561
+ capture.state === "busy" ? /* @__PURE__ */ jsx(SpecCaptureLoading, {}) : /* @__PURE__ */ jsx(
1502
1562
  "iframe",
1503
1563
  {
1504
1564
  src: status.overlay_url,
@@ -3319,7 +3379,7 @@ function NavAppShell({
3319
3379
  /* @__PURE__ */ jsxs("div", { className: "lg:pl-[240px]", children: [
3320
3380
  /* @__PURE__ */ jsx(NavTopBar, { ...topBar, onOpenNav: () => setNavOpen(true), navOpen }),
3321
3381
  betaBanner ? /* @__PURE__ */ jsx(NavBetaBanner, { ...betaBanner }) : null,
3322
- contentWrapper === "bare" ? children : /* @__PURE__ */ jsx("main", { className: "px-6 py-7", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl", children }) })
3382
+ contentWrapper === "bare" ? /* @__PURE__ */ jsx("div", { "data-shell-content": "", children }) : /* @__PURE__ */ jsx("main", { "data-shell-content": "", className: "px-6 py-7", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl", children }) })
3323
3383
  ] })
3324
3384
  ]
3325
3385
  }
@@ -3327,5 +3387,5 @@ function NavAppShell({
3327
3387
  }
3328
3388
 
3329
3389
  export { BETA_BANNER_DISMISS_KEY, CAMPAIGN_FEATURE_AVAILABILITY, CORE_APP_ORIGIN, CORE_BILLING_PATH, FuelAction, NavAppShell, NavBetaBanner, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SPECS_ORIGIN, SpecOverlayAction, SupportAction, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, clearBetaBannerDismissal, envChipClasses, isNavItemActive, resolveOrgLogoUrl, standardPolitogyNav, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeFetcher, useThemePreference };
3330
- //# sourceMappingURL=chunk-G5DXKB3R.js.map
3331
- //# sourceMappingURL=chunk-G5DXKB3R.js.map
3390
+ //# sourceMappingURL=chunk-2SF25TVU.js.map
3391
+ //# sourceMappingURL=chunk-2SF25TVU.js.map