@flamingo-stack/openframe-frontend-core 0.0.470-snapshot.20260723224414 → 0.0.470
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/{chunk-UZEL4E6C.cjs → chunk-27ZGHJDS.cjs} +12 -12
- package/dist/{chunk-UZEL4E6C.cjs.map → chunk-27ZGHJDS.cjs.map} +1 -1
- package/dist/{chunk-MWCBGMIB.cjs → chunk-2W3BAKMP.cjs} +11 -11
- package/dist/{chunk-MWCBGMIB.cjs.map → chunk-2W3BAKMP.cjs.map} +1 -1
- package/dist/{chunk-V3NBBI7J.js → chunk-6CITW5BD.js} +2 -2
- package/dist/{chunk-TR7C4MQA.js → chunk-6O47KXGR.js} +3 -3
- package/dist/{chunk-HQL5GJ35.cjs → chunk-6TI3DCOG.cjs} +3 -3
- package/dist/{chunk-HQL5GJ35.cjs.map → chunk-6TI3DCOG.cjs.map} +1 -1
- package/dist/{chunk-L4642LKV.js → chunk-7HNLEZ6N.js} +6 -6
- package/dist/{chunk-YQWJMZDI.js → chunk-7WT3K34L.js} +6 -6
- package/dist/{chunk-Z472XQ53.js → chunk-AYNDMF5C.js} +3 -3
- package/dist/{chunk-MJM2WIHI.cjs → chunk-C667B74I.cjs} +23 -23
- package/dist/{chunk-MJM2WIHI.cjs.map → chunk-C667B74I.cjs.map} +1 -1
- package/dist/{chunk-N2S7U3XO.cjs → chunk-CGUYR6DR.cjs} +1606 -1606
- package/dist/{chunk-N2S7U3XO.cjs.map → chunk-CGUYR6DR.cjs.map} +1 -1
- package/dist/{chunk-BAZW4H7E.cjs → chunk-DK42EGBQ.cjs} +22 -22
- package/dist/{chunk-BAZW4H7E.cjs.map → chunk-DK42EGBQ.cjs.map} +1 -1
- package/dist/{chunk-A62C4C4V.cjs → chunk-FIG2RKZF.cjs} +2 -11
- package/dist/chunk-FIG2RKZF.cjs.map +1 -0
- package/dist/{chunk-OB7YDOTK.cjs → chunk-GHHUCCLX.cjs} +8 -8
- package/dist/{chunk-OB7YDOTK.cjs.map → chunk-GHHUCCLX.cjs.map} +1 -1
- package/dist/{chunk-C2B7QBBS.js → chunk-H5SCS5XW.js} +5 -5
- package/dist/{chunk-FPOHUGEK.js → chunk-ILUX3DXS.js} +2 -2
- package/dist/{chunk-TONMD7QX.cjs → chunk-IOHQJOLR.cjs} +38 -38
- package/dist/{chunk-TONMD7QX.cjs.map → chunk-IOHQJOLR.cjs.map} +1 -1
- package/dist/{chunk-KOSZ7UCE.js → chunk-JI24IMOW.js} +4 -4
- package/dist/{chunk-JJTXPBO6.cjs → chunk-MAZGXV4D.cjs} +86 -86
- package/dist/{chunk-JJTXPBO6.cjs.map → chunk-MAZGXV4D.cjs.map} +1 -1
- package/dist/{chunk-Y6RZRNIO.cjs → chunk-MCO55ART.cjs} +93 -93
- package/dist/{chunk-Y6RZRNIO.cjs.map → chunk-MCO55ART.cjs.map} +1 -1
- package/dist/{chunk-OTV4A6NA.js → chunk-MK2YWJAA.js} +3 -3
- package/dist/{chunk-USJAU7YT.js → chunk-NPN4WYOI.js} +5 -5
- package/dist/{chunk-KBISUUSX.cjs → chunk-O7N2PGM2.cjs} +40 -40
- package/dist/{chunk-KBISUUSX.cjs.map → chunk-O7N2PGM2.cjs.map} +1 -1
- package/dist/{chunk-ADN7C6II.cjs → chunk-PYR4LVAF.cjs} +50 -50
- package/dist/{chunk-ADN7C6II.cjs.map → chunk-PYR4LVAF.cjs.map} +1 -1
- package/dist/{chunk-62SPXX2E.js → chunk-QA5LN5NS.js} +3 -3
- package/dist/{chunk-HHD2HN7A.js → chunk-QNWYTEM6.js} +8 -8
- package/dist/{chunk-AEFKXAC2.cjs → chunk-RRCWF5N3.cjs} +33 -33
- package/dist/{chunk-AEFKXAC2.cjs.map → chunk-RRCWF5N3.cjs.map} +1 -1
- package/dist/{chunk-MTQYQQEK.js → chunk-V3SOAVX4.js} +6 -6
- package/dist/{chunk-USI4MAGI.cjs → chunk-V6OVMRXL.cjs} +73 -73
- package/dist/{chunk-USI4MAGI.cjs.map → chunk-V6OVMRXL.cjs.map} +1 -1
- package/dist/{chunk-6VT6CRRI.js → chunk-VWNYNJWU.js} +4 -4
- package/dist/{chunk-BQVLGIXY.cjs → chunk-WPNVOQ4O.cjs} +12 -12
- package/dist/{chunk-BQVLGIXY.cjs.map → chunk-WPNVOQ4O.cjs.map} +1 -1
- package/dist/{chunk-DTAZYY7W.cjs → chunk-WSKUYW3G.cjs} +32 -32
- package/dist/{chunk-DTAZYY7W.cjs.map → chunk-WSKUYW3G.cjs.map} +1 -1
- package/dist/{chunk-4GPWLGKT.js → chunk-XTCBRQN2.js} +2 -11
- package/dist/{chunk-4GPWLGKT.js.map → chunk-XTCBRQN2.js.map} +1 -1
- package/dist/{chunk-SZMA65OW.js → chunk-Z6NIMOXQ.js} +2 -2
- package/dist/{chunk-4DZQ5BF7.js → chunk-ZWVQULAJ.js} +3 -3
- package/dist/components/case-studies/index.cjs +16 -16
- package/dist/components/case-studies/index.js +6 -6
- package/dist/components/chat/index.cjs +6 -6
- package/dist/components/chat/index.js +5 -5
- package/dist/components/contact/index.cjs +7 -7
- package/dist/components/contact/index.js +6 -6
- package/dist/components/docs/index.cjs +9 -9
- package/dist/components/docs/index.js +8 -8
- package/dist/components/embeds/index.cjs +7 -7
- package/dist/components/embeds/index.js +6 -6
- package/dist/components/faq/index.cjs +8 -8
- package/dist/components/faq/index.js +7 -7
- package/dist/components/features/index.cjs +6 -6
- package/dist/components/features/index.js +5 -5
- package/dist/components/help-center-pages/index.cjs +25 -25
- package/dist/components/help-center-pages/index.js +16 -16
- package/dist/components/icons/index.cjs +3 -3
- package/dist/components/icons/index.js +2 -2
- package/dist/components/index.cjs +244 -244
- package/dist/components/index.js +14 -14
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +10 -10
- package/dist/components/onboarding-guides/index.js +9 -9
- package/dist/components/related-content/index.cjs +8 -8
- package/dist/components/related-content/index.js +7 -7
- package/dist/components/tickets/index.cjs +9 -9
- package/dist/components/tickets/index.js +8 -8
- package/dist/components/ui/file-manager/index.cjs +69 -69
- package/dist/components/ui/file-manager/index.js +2 -2
- package/dist/components/ui/index.cjs +6 -6
- package/dist/components/ui/index.js +5 -5
- package/dist/hooks/index.cjs +4 -4
- package/dist/hooks/index.js +3 -3
- package/dist/index.cjs +6 -6
- package/dist/index.js +5 -5
- package/dist/types/index.cjs +1 -10
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -10
- package/dist/types/index.js.map +1 -1
- package/dist/utils/cn.d.ts.map +1 -1
- package/dist/utils/index.cjs +1 -10
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +1 -10
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/cn.ts +1 -27
- package/dist/chunk-A62C4C4V.cjs.map +0 -1
- /package/dist/{chunk-V3NBBI7J.js.map → chunk-6CITW5BD.js.map} +0 -0
- /package/dist/{chunk-TR7C4MQA.js.map → chunk-6O47KXGR.js.map} +0 -0
- /package/dist/{chunk-L4642LKV.js.map → chunk-7HNLEZ6N.js.map} +0 -0
- /package/dist/{chunk-YQWJMZDI.js.map → chunk-7WT3K34L.js.map} +0 -0
- /package/dist/{chunk-Z472XQ53.js.map → chunk-AYNDMF5C.js.map} +0 -0
- /package/dist/{chunk-C2B7QBBS.js.map → chunk-H5SCS5XW.js.map} +0 -0
- /package/dist/{chunk-FPOHUGEK.js.map → chunk-ILUX3DXS.js.map} +0 -0
- /package/dist/{chunk-KOSZ7UCE.js.map → chunk-JI24IMOW.js.map} +0 -0
- /package/dist/{chunk-OTV4A6NA.js.map → chunk-MK2YWJAA.js.map} +0 -0
- /package/dist/{chunk-USJAU7YT.js.map → chunk-NPN4WYOI.js.map} +0 -0
- /package/dist/{chunk-62SPXX2E.js.map → chunk-QA5LN5NS.js.map} +0 -0
- /package/dist/{chunk-HHD2HN7A.js.map → chunk-QNWYTEM6.js.map} +0 -0
- /package/dist/{chunk-MTQYQQEK.js.map → chunk-V3SOAVX4.js.map} +0 -0
- /package/dist/{chunk-6VT6CRRI.js.map → chunk-VWNYNJWU.js.map} +0 -0
- /package/dist/{chunk-SZMA65OW.js.map → chunk-Z6NIMOXQ.js.map} +0 -0
- /package/dist/{chunk-4DZQ5BF7.js.map → chunk-ZWVQULAJ.js.map} +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
var
|
|
14
|
+
var _chunkCGUYR6DRcjs = require('./chunk-CGUYR6DR.cjs');
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ var _chunkSSJNN5BScjs = require('./chunk-SSJNN5BS.cjs');
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
var
|
|
24
|
+
var _chunkPYR4LVAFcjs = require('./chunk-PYR4LVAF.cjs');
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
@@ -29,7 +29,7 @@ var _chunkWBR7H6E3cjs = require('./chunk-WBR7H6E3.cjs');
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
var
|
|
32
|
+
var _chunkFIG2RKZFcjs = require('./chunk-FIG2RKZF.cjs');
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
|
|
@@ -69,7 +69,7 @@ function UnifiedPagination({
|
|
|
69
69
|
};
|
|
70
70
|
if (totalPages <= 1) return null;
|
|
71
71
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
72
|
-
|
|
72
|
+
_chunkCGUYR6DRcjs.Pagination,
|
|
73
73
|
{
|
|
74
74
|
currentPage,
|
|
75
75
|
totalPages,
|
|
@@ -82,12 +82,12 @@ var init_unified_pagination = _chunkVRHGVLSLcjs.__esm.call(void 0, {
|
|
|
82
82
|
"use strict";
|
|
83
83
|
"use client";
|
|
84
84
|
_chunkSSJNN5BScjs.init_next_navigation.call(void 0, );
|
|
85
|
-
|
|
85
|
+
_chunkCGUYR6DRcjs.init_pagination.call(void 0, );
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
// src/components/empty-state.tsx
|
|
90
|
-
|
|
90
|
+
_chunkPYR4LVAFcjs.init_button2.call(void 0, );
|
|
91
91
|
_chunkSSJNN5BScjs.init_next_navigation.call(void 0, );
|
|
92
92
|
var _lucidereact = require('lucide-react');
|
|
93
93
|
|
|
@@ -213,7 +213,7 @@ function EmptyState({
|
|
|
213
213
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h2", { className: "mb-2 md:mb-3 text-h3 text-ods-text-primary", children: displayTitle }),
|
|
214
214
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "mb-4 md:mb-8 max-w-md text-h6 text-ods-text-secondary", children: displayDescription }),
|
|
215
215
|
showCTA && smartCTA && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full max-w-xs mb-3", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
216
|
-
|
|
216
|
+
_chunkPYR4LVAFcjs.Button,
|
|
217
217
|
{
|
|
218
218
|
onClick: smartCTA.action,
|
|
219
219
|
className: ctaVariant === "primary" ? "w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-body font-medium" : "w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-body font-medium",
|
|
@@ -221,7 +221,7 @@ function EmptyState({
|
|
|
221
221
|
}
|
|
222
222
|
) }),
|
|
223
223
|
showBackButton && onGoBack && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full max-w-xs", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
224
|
-
|
|
224
|
+
_chunkPYR4LVAFcjs.Button,
|
|
225
225
|
{
|
|
226
226
|
onClick: onGoBack,
|
|
227
227
|
variant: "outline",
|
|
@@ -236,7 +236,7 @@ function EmptyState({
|
|
|
236
236
|
var _react = require('react');
|
|
237
237
|
|
|
238
238
|
function DevSectionView({ sectionKey, hero, preControls, children, showHeading = true }) {
|
|
239
|
-
const section =
|
|
239
|
+
const section = _chunkCGUYR6DRcjs.OPENFRAME_DEV_SECTIONS[sectionKey];
|
|
240
240
|
const router = _chunkSSJNN5BScjs.useRouter.call(void 0, );
|
|
241
241
|
const pathname = _chunkSSJNN5BScjs.usePathname.call(void 0, );
|
|
242
242
|
const searchParams = _chunkSSJNN5BScjs.useSearchParams.call(void 0, );
|
|
@@ -276,7 +276,7 @@ function DevSectionView({ sectionKey, hero, preControls, children, showHeading =
|
|
|
276
276
|
preControls,
|
|
277
277
|
(search || filter) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
|
|
278
278
|
search && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
279
|
-
|
|
279
|
+
_chunkCGUYR6DRcjs.SearchInput,
|
|
280
280
|
{
|
|
281
281
|
showDropdown: false,
|
|
282
282
|
placeholder: search.placeholder,
|
|
@@ -286,7 +286,7 @@ function DevSectionView({ sectionKey, hero, preControls, children, showHeading =
|
|
|
286
286
|
}
|
|
287
287
|
),
|
|
288
288
|
filter && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
289
|
-
|
|
289
|
+
_chunkCGUYR6DRcjs.StatusFilterComponent,
|
|
290
290
|
{
|
|
291
291
|
selectedStatus: currentFilterValue,
|
|
292
292
|
onStatusChange: handleFilterChange,
|
|
@@ -311,7 +311,7 @@ function DevSectionPage({
|
|
|
311
311
|
shell = true
|
|
312
312
|
}) {
|
|
313
313
|
const router = _chunkSSJNN5BScjs.useRouter.call(void 0, );
|
|
314
|
-
const section =
|
|
314
|
+
const section = _chunkCGUYR6DRcjs.OPENFRAME_DEV_SECTIONS[sectionKey];
|
|
315
315
|
const backCfg = backButton === false ? void 0 : {
|
|
316
316
|
label: _nullishCoalesce((backButton ? backButton.label : void 0), () => ( "Back to home")),
|
|
317
317
|
onClick: () => router.push(_nullishCoalesce((backButton ? backButton.href : void 0), () => ( "/")))
|
|
@@ -324,7 +324,7 @@ function DevSectionPage({
|
|
|
324
324
|
// duplicate title. (The hero icon is intentionally dropped: TitleBlock is
|
|
325
325
|
// frozen and renders title-only.)
|
|
326
326
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
327
|
-
|
|
327
|
+
_chunkCGUYR6DRcjs.PageLayout,
|
|
328
328
|
{
|
|
329
329
|
title: _nullishCoalesce(title, () => ( section.hero.title)),
|
|
330
330
|
subtitle: _nullishCoalesce(subtitle, () => ( section.hero.description)),
|
|
@@ -335,7 +335,7 @@ function DevSectionPage({
|
|
|
335
335
|
}
|
|
336
336
|
)
|
|
337
337
|
);
|
|
338
|
-
return shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
338
|
+
return shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkCGUYR6DRcjs.PageShell, { children: inner }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "page-shell-content", children: inner });
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
// src/components/shared/dev-section/dev-card-row.tsx
|
|
@@ -379,7 +379,7 @@ function DevCardRowSkeletonList({ rows = 5 }) {
|
|
|
379
379
|
|
|
380
380
|
// src/components/shared/delivery/delivery-row.tsx
|
|
381
381
|
_chunkWBR7H6E3cjs.init_next_link.call(void 0, );
|
|
382
|
-
|
|
382
|
+
_chunkFIG2RKZFcjs.init_cn.call(void 0, );
|
|
383
383
|
|
|
384
384
|
function getRelativeTime(timestamp) {
|
|
385
385
|
const now = Date.now();
|
|
@@ -400,9 +400,9 @@ function DeliveryRow({
|
|
|
400
400
|
className
|
|
401
401
|
}) {
|
|
402
402
|
const taskType = item.taskType;
|
|
403
|
-
const typeBadgeLabel =
|
|
404
|
-
const typeBadgeTextColor =
|
|
405
|
-
const statusBadgeScheme =
|
|
403
|
+
const typeBadgeLabel = _chunkCGUYR6DRcjs.TASK_TYPE_LABELS[taskType] || "TASK";
|
|
404
|
+
const typeBadgeTextColor = _chunkCGUYR6DRcjs.TASK_TYPE_TEXT_COLORS[taskType] || "";
|
|
405
|
+
const statusBadgeScheme = _chunkCGUYR6DRcjs.getStatusColorScheme.call(void 0, item.status);
|
|
406
406
|
const relativeTime = getRelativeTime(item.dateUpdated);
|
|
407
407
|
const subtitle = `ACTIVE ${relativeTime}${item.listNames.length > 0 ? `, ${item.listNames.join(", ")}` : ""}, ${item.id}`;
|
|
408
408
|
const inner = /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full", children: [
|
|
@@ -414,7 +414,7 @@ function DeliveryRow({
|
|
|
414
414
|
] }),
|
|
415
415
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex-shrink-0 self-start flex flex-col gap-2", children: [
|
|
416
416
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
417
|
-
|
|
417
|
+
_chunkCGUYR6DRcjs.StatusBadge,
|
|
418
418
|
{
|
|
419
419
|
text: item.status.toUpperCase(),
|
|
420
420
|
colorScheme: statusBadgeScheme,
|
|
@@ -423,7 +423,7 @@ function DeliveryRow({
|
|
|
423
423
|
}
|
|
424
424
|
),
|
|
425
425
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
426
|
-
|
|
426
|
+
_chunkCGUYR6DRcjs.StatusBadge,
|
|
427
427
|
{
|
|
428
428
|
text: typeBadgeLabel,
|
|
429
429
|
variant: "card",
|
|
@@ -432,7 +432,7 @@ function DeliveryRow({
|
|
|
432
432
|
)
|
|
433
433
|
] })
|
|
434
434
|
] });
|
|
435
|
-
const baseClass =
|
|
435
|
+
const baseClass = _chunkFIG2RKZFcjs.cn.call(void 0,
|
|
436
436
|
"block p-[12px] md:p-[16px] no-underline text-inherit transition-colors duration-150",
|
|
437
437
|
// `scroll-mt-24` is paid for whether `id` is set or not (it's a
|
|
438
438
|
// single Tailwind utility, no runtime cost). Keeping it
|
|
@@ -460,4 +460,4 @@ function DeliveryRow({
|
|
|
460
460
|
|
|
461
461
|
|
|
462
462
|
exports.EmptyState = EmptyState; exports.UnifiedPagination = UnifiedPagination; exports.unified_pagination_exports = unified_pagination_exports; exports.init_unified_pagination = init_unified_pagination; exports.DevSectionView = DevSectionView; exports.DevSectionPage = DevSectionPage; exports.DevCardRowContent = DevCardRowContent; exports.DevCardRowSkeleton = DevCardRowSkeleton; exports.DevCardRowSkeletonList = DevCardRowSkeletonList; exports.DeliveryRow = DeliveryRow;
|
|
463
|
-
//# sourceMappingURL=chunk-
|
|
463
|
+
//# sourceMappingURL=chunk-DK42EGBQ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BAZW4H7E.cjs","../src/components/unified-pagination.tsx","../src/components/empty-state.tsx","../src/components/shared/dev-section/dev-section-view.tsx","../src/components/shared/dev-section/dev-section-page.tsx","../src/components/shared/dev-section/dev-card-row.tsx","../src/components/shared/delivery/delivery-row.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BAZW4H7E.cjs","sourcesContent":[null,"\"use client\"\n\nimport { useRouter, useSearchParams, usePathname } from \"../embed-shims/next-navigation\"\nimport { Pagination } from \"./pagination\"\n\ninterface UnifiedPaginationProps {\n currentPage: number\n totalPages: number\n onPageChange?: (page: number) => void\n className?: string\n}\n\nexport function UnifiedPagination({ \n currentPage, \n totalPages, \n onPageChange,\n className = \"mt-8 flex justify-center w-full\"\n}: UnifiedPaginationProps) {\n const router = useRouter()\n const searchParams = useSearchParams()\n const pathname = usePathname()\n\n const handlePageChange = (page: number) => {\n // Preserve current scroll position\n const currentScrollY = window.scrollY\n \n // Call the callback to update local state (prevents reload)\n if (onPageChange) {\n onPageChange(page)\n }\n \n // Update URL for bookmarking without navigation\n const params = new URLSearchParams(searchParams.toString())\n params.set(\"page\", page.toString())\n \n // Update URL without navigation (for bookmarking support)\n const newUrl = `${pathname}?${params.toString()}`\n window.history.replaceState(null, '', newUrl)\n \n // Restore scroll position after a brief delay to allow content to render\n setTimeout(() => {\n window.scrollTo({\n top: currentScrollY,\n behavior: 'instant' // Instant to prevent any scroll animation\n })\n }, 0)\n }\n\n // Don't render pagination if there's only one page\n if (totalPages <= 1) return null\n\n return (\n <div className={className}>\n <Pagination\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n />\n </div>\n )\n} ","\"use client\";\n\nimport { Search, FileText, Package } from \"lucide-react\"\nimport { Button } from \"./ui/button\"\nimport { useRouter } from \"../embed-shims/next-navigation\"\n\nexport interface EmptyStateProps {\n type: 'vendors' | 'posts' | 'search' | 'generic'\n title?: string\n description?: string\n showBackButton?: boolean\n onGoBack?: () => void\n backButtonText?: string\n // New CTA properties\n showCTA?: boolean\n ctaText?: string\n onCtaClick?: () => void\n ctaVariant?: 'primary' | 'secondary'\n}\n\nexport function EmptyState({\n type,\n title,\n description,\n showBackButton = false,\n onGoBack,\n backButtonText = \"Go Back\",\n showCTA = true,\n ctaText,\n onCtaClick,\n ctaVariant = 'primary'\n}: EmptyStateProps) {\n const router = useRouter()\n\n // Default content based on type\n const getDefaultContent = () => {\n switch (type) {\n case 'vendors':\n return {\n icon: <Package className=\"w-full h-full\" />,\n title: \"No vendors found\",\n description: \"We couldn't find any vendors matching your criteria. Try adjusting your filters or search terms.\"\n }\n case 'posts':\n return {\n icon: <FileText className=\"w-full h-full\" />,\n title: \"No articles found\",\n description: \"We couldn't find any articles matching your criteria. Try different categories, tags, or search terms.\"\n }\n case 'search':\n return {\n icon: <Search className=\"w-full h-full\" />,\n title: \"No results found\",\n description: \"Your search didn't return any results. Try different keywords or browse our categories.\"\n }\n default:\n return {\n icon: <Search className=\"w-full h-full\" />,\n title: \"Nothing found\",\n description: \"We couldn't find what you're looking for. Try adjusting your search or filters.\"\n }\n }\n }\n\n // Smart CTA logic based on context\n const getSmartCTA = () => {\n // If custom CTA is provided, use it\n if (ctaText && onCtaClick) {\n return {\n text: ctaText,\n action: onCtaClick\n }\n }\n\n // Check if we're on the client side\n const isClient = typeof window !== 'undefined'\n const currentPath = isClient ? window.location.pathname : ''\n\n // Smart defaults based on type and context\n switch (type) {\n case 'search':\n return {\n text: \"Reset Filters\",\n action: () => {\n if (isClient) {\n // Try to reset search by clearing URL params and refreshing\n const url = new URL(window.location.href)\n url.search = ''\n router.push(url.pathname)\n }\n }\n }\n case 'posts':\n // If we're on blog/community pages, reset blog filters\n if (currentPath.includes('/blog')) {\n return {\n text: \"Reset Filters\",\n action: () => {\n if (isClient) {\n // Reset blog search and filters by clearing URL params\n const url = new URL(window.location.href)\n url.search = ''\n router.push(url.pathname)\n }\n }\n }\n } else if (currentPath.includes('/profile')) {\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n }\n return {\n text: \"View All Posts\",\n action: () => router.push('/blog')\n }\n case 'vendors':\n // If we're in profile or other pages, direct to main content\n if (currentPath.includes('/profile')) {\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n } else if (currentPath.includes('/vendors') || currentPath.includes('/margin-increase/compare')) {\n return {\n text: \"Reset Filters\",\n action: () => {\n if (isClient) {\n // Reset vendor search and filters by clearing URL params\n const url = new URL(window.location.href)\n url.search = ''\n router.push(url.pathname)\n }\n }\n }\n }\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n default:\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n }\n }\n\n const defaultContent = getDefaultContent()\n const displayTitle = title || defaultContent.title\n const displayDescription = description || defaultContent.description\n const smartCTA = getSmartCTA()\n\n return (\n <div className=\"flex flex-col items-center justify-center py-6 md:py-16 px-6 text-center\">\n {/* Icon */}\n <div className=\"mb-3 md:mb-6 flex items-center justify-center\">\n <div className=\"rounded-full bg-ods-card p-3 md:p-6 border border-ods-border\">\n <div className=\"w-8 h-8 md:w-16 md:h-16 text-ods-text-secondary flex items-center justify-center\">\n {defaultContent.icon}\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h2 className=\"mb-2 md:mb-3 text-h3 text-ods-text-primary\">\n {displayTitle}\n </h2>\n\n {/* Description */}\n <p className=\"mb-4 md:mb-8 max-w-md text-h6 text-ods-text-secondary\">\n {displayDescription}\n </p>\n\n {/* Smart CTA Button */}\n {showCTA && smartCTA && (\n <div className=\"w-full max-w-xs mb-3\">\n <Button\n onClick={smartCTA.action}\n className={ctaVariant === 'primary'\n ? \"w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-body font-medium\"\n : \"w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-body font-medium\"\n }\n >\n {smartCTA.text}\n </Button>\n </div>\n )}\n\n {/* Optional Back Button */}\n {showBackButton && onGoBack && (\n <div className=\"w-full max-w-xs\">\n <Button\n onClick={onGoBack}\n variant=\"outline\"\n className=\"w-full transition-all duration-150 font-body font-medium\"\n >\n {backButtonText}\n </Button>\n </div>\n )}\n </div>\n )\n} ","'use client';\n\n/**\n * DevSectionView — the canonical chrome for ANY dev-center section\n * (Roadmap / Delivery / Releases). One component, used in BOTH:\n *\n * - tabbed `/roadmap-and-releases` (compact title mode, no `hero`)\n * - full-page `/roadmap`, `/bug-fixes-and-enhancements`, `/releases`\n * (hero mode with icon + description + back link)\n *\n * Owns: title rendering, the inline search input, the filter pill row,\n * and the URL-param wiring that connects both. The list `children`\n * receive a clean URL contract — they read `?<paramKey>=...` via\n * `useSearchParams()` and refetch on change. No duplicated controls.\n */\n\nimport type { ReactNode } from 'react';\nimport { useState, useEffect } from 'react';\nimport { useRouter, useSearchParams, usePathname } from '../../../embed-shims';\nimport { SearchInput } from '../../ui';\nimport { StatusFilterComponent } from '../../features';\nimport {\n OPENFRAME_DEV_SECTIONS,\n type OpenframeDevSectionKey,\n} from '../../../utils/dev-sections/openframe-dev-sections';\n\nexport interface DevSectionViewProps {\n /** Which section to render — drives title, search, and filter\n * config via the `OPENFRAME_DEV_SECTIONS` registry. */\n sectionKey: OpenframeDevSectionKey;\n /** When set, renders the rich page-level hero (icon + h1 + description).\n * Omit for the compact tab-context heading. */\n hero?: {\n /** Pre-rendered icon JSX. Server components render the icon themselves\n * and pass the element here — function references can't cross the\n * server→client boundary, but React elements can. */\n icon: ReactNode;\n /** Hero title. Falls back to `OPENFRAME_DEV_SECTIONS[sectionKey].hero.title`\n * when omitted, so embedders can override the (OpenFrame-specific) default\n * copy without forking the registry. */\n title?: string;\n description: string;\n };\n /** Optional slot rendered BETWEEN the hero and the search/filter\n * controls. Use this for an entry-action surface that should sit\n * above the list (e.g. the Help Center's \"Open a new ticket\" form).\n * The slot is wrapped in the same `gap-10` flex column so spacing\n * matches the surrounding chrome — callers should NOT add their\n * own top/bottom margin. Renders `null` (no DOM) when omitted. */\n preControls?: ReactNode;\n /** The page-specific list body. Reads URL params written by this\n * component (search input + filter pills). */\n children: ReactNode;\n /** Render this component's own title heading (hero or compact `h2`). Default\n * `true` — the tab context renders the compact heading itself. Pass `false`\n * when the host already renders the title elsewhere (e.g. `DevSectionPage`\n * routes it through the unified `PageLayout` `TitleBlock`): then only the\n * search + filter controls + list render, with no duplicate heading. */\n showHeading?: boolean;\n}\n\nexport function DevSectionView({ sectionKey, hero, preControls, children, showHeading = true }: DevSectionViewProps) {\n const section = OPENFRAME_DEV_SECTIONS[sectionKey];\n const router = useRouter();\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = section.search;\n const filter = section.filter;\n\n const currentSearch = search ? searchParams.get(search.paramKey) || '' : '';\n const currentFilterValue = filter\n ? searchParams.get(filter.paramKey) || filter.defaultValue\n : '';\n\n // Controlled search-input state — input commits to the URL only on\n // Enter (not on every keystroke), preserving the legacy behavior.\n // Lazy init from URL avoids a brief flash of stale value on first\n // paint after URL-driven re-render (e.g. tab switch).\n const [searchValue, setSearchValue] = useState(() => currentSearch);\n useEffect(() => {\n setSearchValue(currentSearch);\n }, [currentSearch]);\n\n const handleSearchSubmit = (value: string) => {\n if (!search) return;\n const params = new URLSearchParams(searchParams.toString());\n if (value.trim()) params.set(search.paramKey, value.trim());\n else params.delete(search.paramKey);\n router.replace(`${pathname}?${params.toString()}`, { scroll: false });\n };\n\n const handleFilterChange = (value: string) => {\n if (!filter) return;\n const params = new URLSearchParams(searchParams.toString());\n if (value === filter.defaultValue) params.delete(filter.paramKey);\n else params.set(filter.paramKey, value);\n router.replace(`${pathname}?${params.toString()}`, { scroll: false });\n };\n\n return (\n <div className=\"w-full flex flex-col gap-10\">\n {showHeading &&\n (hero ? (\n <div className=\"space-y-4\">\n <h1 className=\"text-h1 tracking-[-1.12px] text-ods-text-primary flex items-center gap-3\">\n {hero.icon}\n {hero.title ?? section.hero.title}\n </h1>\n <p className=\"text-h4 text-ods-text-secondary max-w-3xl\">\n {hero.description}\n </p>\n </div>\n ) : (\n <div className=\"flex items-center justify-between w-full\">\n <h2 className=\"font-['Azeret_Mono'] font-semibold text-[32px] md:text-[40px] lg:text-[48px] leading-[40px] md:leading-[48px] lg:leading-[56px] text-ods-text-primary tracking-[-0.64px] md:tracking-[-0.8px] lg:tracking-[-0.96px]\">\n {section.hero.title}\n <span className=\"text-ods-accent\">:</span>\n </h2>\n </div>\n ))}\n\n {preControls}\n\n {(search || filter) && (\n <div className=\"space-y-4\">\n {search && (\n <SearchInput\n showDropdown={false}\n placeholder={search.placeholder}\n value={searchValue}\n onChange={setSearchValue}\n onSubmit={handleSearchSubmit}\n />\n )}\n {filter && (\n <StatusFilterComponent\n selectedStatus={currentFilterValue}\n onStatusChange={handleFilterChange}\n statusOptions={[...filter.options]}\n />\n )}\n </div>\n )}\n\n {children}\n </div>\n );\n}\n","'use client';\n\n/**\n * DevSectionPage — full-page wrapper for a dev-center section\n * (`/roadmap`, `/bug-fixes-and-enhancements`, `/releases`).\n *\n * Mounts the lib's canonical `PageLayout` directly (no in-app wrapper)\n * so the back-button affordance stays in lockstep with whatever the\n * design system ships — any future lib change to BackButton / TitleBlock\n * propagates automatically.\n *\n * Composition: `PageShell` → `PageLayout` (back-to-home wired) →\n * `DevSectionView` (icon hero + search + filter pills) → list body.\n *\n * Adding a new section is one entry in `OPENFRAME_DEV_SECTIONS` plus a\n * single-line page file mounting this factory with the new key.\n */\n\nimport type { ReactNode } from 'react';\nimport { useRouter } from '../../../embed-shims/next-navigation';\nimport { PageShell, PageLayout } from '../../ui';\nimport { DevSectionView } from './dev-section-view';\nimport {\n OPENFRAME_DEV_SECTIONS,\n type OpenframeDevSectionKey,\n} from '../../../utils/dev-sections/openframe-dev-sections';\n\nexport interface DevSectionPageProps {\n sectionKey: OpenframeDevSectionKey;\n /** The page-specific list body (e.g. `<RoadmapList />`). */\n children: ReactNode;\n /** Optional slot rendered BETWEEN the hero and search/filter — see\n * `DevSectionView.preControls`. Used by surfaces that want an entry\n * action (e.g. Help Center's \"Open a new ticket\" form) above the\n * controls instead of below them. */\n preControls?: ReactNode;\n /** Back-button config — same shape as `LegalDocumentPage` /\n * `ReleaseDetailPage`. Pass `false` to hide entirely. Default\n * `{ label: 'Back to home', href: '/' }` — embedders whose \"home\" isn't `/`\n * should override `href`, or pass `false` if the embed has no home page. */\n backButton?: { label?: string; href?: string } | false;\n /** Override the hero title. Defaults to the (OpenFrame-specific) copy in\n * `OPENFRAME_DEV_SECTIONS[sectionKey].hero.title`. Set this to brand the\n * section for a non-OpenFrame embed. */\n title?: string;\n /** Override the hero subtitle/description. Defaults to\n * `OPENFRAME_DEV_SECTIONS[sectionKey].hero.description`. */\n subtitle?: string;\n /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n * `true` — the contract for marketing/hub surfaces with no app shell. Pass\n * `false` when the host layout already provides the page container (e.g.\n * openframe-frontend's `AppLayout` `<main>`): then only the\n * `page-shell-content` padding box is rendered, avoiding a nested `<main>`. */\n shell?: boolean;\n}\n\nexport function DevSectionPage({\n sectionKey,\n children,\n preControls,\n backButton,\n title,\n subtitle,\n shell = true,\n}: DevSectionPageProps) {\n const router = useRouter();\n const section = OPENFRAME_DEV_SECTIONS[sectionKey];\n\n // Back-button config — mirrors LegalDocumentPage / ReleaseDetailPage.\n // Default: { label: 'Back to home', href: '/' }. Pass `false` to hide.\n // After `backButton &&` narrowing, inner type is `{ label?, href? } |\n // undefined`; don't re-compare to `false` (TS2367).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: (backButton ? backButton.label : undefined) ?? 'Back to home',\n onClick: () => router.push((backButton ? backButton.href : undefined) ?? '/'),\n };\n\n const inner = (\n // Unified header: title/description route through the canonical (frozen)\n // `PageLayout` `TitleBlock` (text-h2) — same as FAQ / Legal / detail pages —\n // so every help-center surface shares one header. `DevSectionView` then\n // renders ONLY its search + filter controls (`showHeading={false}`), no\n // duplicate title. (The hero icon is intentionally dropped: TitleBlock is\n // frozen and renders title-only.)\n <PageLayout\n title={title ?? section.hero.title}\n subtitle={subtitle ?? section.hero.description}\n titleSize=\"h1\"\n titleWrap\n backButton={backCfg}\n >\n <DevSectionView sectionKey={sectionKey} showHeading={false} preControls={preControls}>\n {children}\n </DevSectionView>\n </PageLayout>\n );\n\n // `shell` true → standalone `<PageShell>` (own <main> + bg + max-width).\n // false → padding-only box (no nested <main>) for hosts whose layout already\n // provides the container; both consume the host's `--page-shell-*` vars.\n return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n","'use client';\n\n/**\n * Shared row chrome for any `DevSectionPage` list (delivery, tickets,\n * future sections). One source of truth for the layout that every\n * dev-section card row uses:\n * left column → title (h3) / subtitle (h5 uppercase) / description\n * (h4 line-clamp-3), each in a fixed min-height block\n * so rows align across the grid\n * right column → caller-supplied stacked badges\n *\n * Surface stays small on purpose — `rightBadges` is a `ReactNode` so\n * the caller decides how many badges (delivery: 2, tickets: 1-2,\n * future: anything). No behavior baked in: the caller wraps the row\n * in a `<div>` (static, like delivery) or `<button>` (clickable, like\n * tickets) and renders the row content via this component.\n *\n * Pair with `DevCardRowSkeletonList` for the loading state — the\n * skeleton mirrors the same min-heights so the in-flight UI doesn't\n * shift the layout when real data lands.\n *\n * NOTE: the ticket conversation row is NOT here — it renders the shared\n * `<ChatMessageRow>` (`components/chat/chat-message-row.tsx`), the SAME\n * component the OpenMSP Slack-community feed uses, so the two surfaces stay\n * pixel-identical by construction.\n */\n\nimport type { ReactNode } from 'react';\n\nexport interface DevCardRowContentProps {\n title: string;\n /** Single-line uppercase metadata (e.g. \"UPDATED today, #4271, Code review\"). */\n subtitle: string;\n /** 3-line description block. Empty string renders the fallback. */\n description: string;\n /** Fallback copy when `description` is empty. Defaults to a generic\n * string; ticket / delivery surfaces override. */\n emptyDescription?: string;\n /** Right column — caller renders its own stacked badges. */\n rightBadges: ReactNode;\n}\n\nexport function DevCardRowContent({\n title,\n subtitle,\n description,\n emptyDescription = 'No description provided',\n rightBadges,\n}: DevCardRowContentProps) {\n return (\n <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n <div className=\"min-h-[24px] flex items-center\">\n <h3 className=\"text-h3 text-ods-text-primary tracking-[-0.36px] flex-1 line-clamp-2 md:truncate break-words\">\n {title}\n </h3>\n </div>\n <div className=\"min-h-[20px] flex items-center\">\n <p className=\"text-h5 text-ods-text-secondary uppercase tracking-[-0.28px] truncate\">\n {subtitle}\n </p>\n </div>\n <div className=\"min-h-[72px] flex items-center\">\n <p className=\"text-h4 text-ods-text-secondary line-clamp-3 break-words\">\n {description || emptyDescription}\n </p>\n </div>\n </div>\n <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n {rightBadges}\n </div>\n </div>\n );\n}\n\n/**\n * Skeleton rendering for a single row — the bars mirror the same\n * min-heights as `DevCardRowContent` so the loading→loaded swap\n * doesn't reflow.\n */\nexport function DevCardRowSkeleton() {\n return (\n <div className=\"border-b border-ods-border last:border-b-0 p-[12px] md:p-[16px]\">\n <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n <div className=\"min-h-[24px] flex items-center\">\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n </div>\n <div className=\"min-h-[20px] flex items-center\">\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-1/2\" />\n </div>\n <div className=\"min-h-[72px] flex items-center\">\n <div className=\"flex-1 space-y-1\">\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-2/3\" />\n </div>\n </div>\n </div>\n <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n <div className=\"h-[32px] w-[100px] bg-ods-border rounded animate-pulse\" />\n <div className=\"h-[32px] w-[120px] bg-ods-border rounded animate-pulse\" />\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * The standard \"5 skeleton rows inside a bordered card\" loading state\n * used by every list shell. Both delivery (`delivery-table.tsx`) and\n * tickets (`tickets-list.tsx`) mount this directly.\n */\nexport function DevCardRowSkeletonList({ rows = 5 }: { rows?: number }) {\n return (\n <div className=\"bg-ods-card border border-ods-border rounded-[6px] overflow-hidden w-full\">\n {Array.from({ length: rows }, (_, i) => (\n <DevCardRowSkeleton key={i} />\n ))}\n </div>\n );\n}\n","'use client'\n\n/**\n * `<DeliveryRow />` — canonical single-row presentation for a ClickUp\n * delivery item.\n *\n * Single source of truth: both the `/bug-fixes-and-enhancements` page\n * (via `DeliveryTable`) AND the linked-delivery card on a HubSpot ticket\n * (via `TicketLinkedDeliveryCard`) compose this primitive. Visual parity\n * across those two surfaces is the design goal — the user reads the\n * card on their ticket and recognises it as a row from the public\n * delivery list.\n *\n * Behaviors:\n * - `href` set → outer element is an `<a>`, the whole row becomes\n * clickable (used by the linked-card surface to deep-link into\n * `/bug-fixes-and-enhancements?focus=<id>`).\n * - `id` set → outer element gets that DOM id so the consuming page\n * can `scrollIntoView` to it when the URL carries `?focus=<id>`.\n * - `highlighted` true → brief accent border + background pulse\n * (`animate-flash-focus` keyframe defined in `tailwind.config.ts`).\n * - `caption` set → small uppercase label rendered above the title\n * (\"LINKED DELIVERY\" on the ticket-side variant). Omitted on the\n * standard list rendering.\n */\n\nimport * as React from 'react'\nimport Link from '../../../embed-shims/next-link'\nimport { StatusBadge } from '../../ui/status-badge'\nimport { getStatusColorScheme } from '../../../utils'\nimport {\n type DeliveryItem,\n TASK_TYPE_LABELS,\n TASK_TYPE_TEXT_COLORS,\n} from '../../../types/delivery'\nimport { cn } from '../../../utils/cn'\n\n/** Same heuristic as DeliveryTable's local helper. Inlined so the row\n * primitive owns its complete rendering contract. */\nfunction getRelativeTime(timestamp: number): string {\n const now = Date.now()\n const diff = now - timestamp\n const days = Math.floor(diff / (1000 * 60 * 60 * 24))\n const weeks = Math.floor(days / 7)\n const months = Math.floor(days / 30)\n if (months > 0) return months === 1 ? 'last month' : `${months} months ago`\n if (weeks > 0) return weeks === 1 ? 'last week' : `${weeks} weeks ago`\n if (days > 0) return days === 1 ? 'yesterday' : `${days} days ago`\n return 'today'\n}\n\nexport interface DeliveryRowProps {\n item: DeliveryItem\n /** When set, the row becomes a clickable anchor. The ticket-side\n * linked-card composes this from `buildDevSectionUrl('delivery', id)`\n * which carries `?search=<id>` — the delivery list filters to that\n * exact task on landing (canonical deep-link mechanism, same one\n * the chat-inline delivery card uses). */\n href?: string\n /** Small uppercase caption rendered above the title. Used by the\n * linked-delivery card variant (\"LINKED DELIVERY\"). */\n caption?: string\n /** DOM `id` applied to the row's outer element. `DeliveryTable`\n * always sets `delivery-<external_id>` so chat-card deep-links\n * (`?search=<id>#delivery-<id>`) and the ticket linked-card path\n * both have a target for `useScrollToHash` to scroll to. Always\n * paired with `scroll-mt-24` on the outer element so the row lands\n * BELOW the sticky chrome after the scroll. */\n id?: string\n className?: string\n}\n\nexport function DeliveryRow({\n item,\n href,\n caption,\n id,\n className,\n}: DeliveryRowProps) {\n const taskType = item.taskType as keyof typeof TASK_TYPE_LABELS\n const typeBadgeLabel = TASK_TYPE_LABELS[taskType] || 'TASK'\n const typeBadgeTextColor = TASK_TYPE_TEXT_COLORS[taskType] || ''\n const statusBadgeScheme = getStatusColorScheme(item.status)\n const relativeTime = getRelativeTime(item.dateUpdated)\n const subtitle = `ACTIVE ${relativeTime}${item.listNames.length > 0 ? `, ${item.listNames.join(', ')}` : ''}, ${item.id}`\n\n const inner = (\n <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n {/* Left: caption (optional) + title + subtitle + description */}\n <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n {caption && (\n <p className=\"text-h5 text-ods-text-secondary\">\n {caption}\n </p>\n )}\n <div className=\"min-h-[24px] md:min-h-[24px] flex items-center\">\n <h3 className=\"text-h3 text-ods-text-primary tracking-[-0.36px] flex-1 line-clamp-2 md:truncate break-words\">\n {item.title}\n </h3>\n </div>\n <div className=\"min-h-[20px] flex items-center\">\n <p className=\"text-h5 text-ods-text-secondary uppercase tracking-[-0.28px] truncate\">\n {subtitle}\n </p>\n </div>\n <div className=\"min-h-[72px] flex items-center\">\n <p className=\"text-h4 text-ods-text-secondary line-clamp-3 break-words\">\n {item.description || 'No description provided'}\n </p>\n </div>\n </div>\n\n {/* Right: status + task-type badges */}\n <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n <StatusBadge\n text={item.status.toUpperCase()}\n colorScheme={statusBadgeScheme}\n variant=\"card\"\n className=\"border border-ods-border\"\n />\n <StatusBadge\n text={typeBadgeLabel}\n variant=\"card\"\n className={`border border-ods-border ${typeBadgeTextColor}`}\n />\n </div>\n </div>\n )\n\n const baseClass = cn(\n 'block p-[12px] md:p-[16px] no-underline text-inherit transition-colors duration-150',\n // `scroll-mt-24` is paid for whether `id` is set or not (it's a\n // single Tailwind utility, no runtime cost). Keeping it\n // unconditional means a future caller adding `id` doesn't also\n // have to remember to ask for the offset.\n 'scroll-mt-24',\n href && 'hover:bg-ods-bg-hover cursor-pointer',\n className,\n )\n\n if (href) {\n // `Link` is the env-aware embed-shim — delegates to `next/link` on\n // a Next.js host (soft RSC nav, back-button restores the previous\n // page's React state intact), falls back to a plain `<a>` on\n // non-Next embedders. A raw `<a href>` was hard-navigating +\n // losing TanStack-Query state on back, leaving /tickets stuck on\n // its skeleton.\n return (\n <Link href={href} id={id} className={baseClass} prefetch={false}>\n {inner}\n </Link>\n )\n }\n\n return <div id={id} className={baseClass}>{inner}</div>\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-DK42EGBQ.cjs","../src/components/unified-pagination.tsx","../src/components/empty-state.tsx","../src/components/shared/dev-section/dev-section-view.tsx","../src/components/shared/dev-section/dev-section-page.tsx","../src/components/shared/dev-section/dev-card-row.tsx","../src/components/shared/delivery/delivery-row.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-DK42EGBQ.cjs","sourcesContent":[null,"\"use client\"\n\nimport { useRouter, useSearchParams, usePathname } from \"../embed-shims/next-navigation\"\nimport { Pagination } from \"./pagination\"\n\ninterface UnifiedPaginationProps {\n currentPage: number\n totalPages: number\n onPageChange?: (page: number) => void\n className?: string\n}\n\nexport function UnifiedPagination({ \n currentPage, \n totalPages, \n onPageChange,\n className = \"mt-8 flex justify-center w-full\"\n}: UnifiedPaginationProps) {\n const router = useRouter()\n const searchParams = useSearchParams()\n const pathname = usePathname()\n\n const handlePageChange = (page: number) => {\n // Preserve current scroll position\n const currentScrollY = window.scrollY\n \n // Call the callback to update local state (prevents reload)\n if (onPageChange) {\n onPageChange(page)\n }\n \n // Update URL for bookmarking without navigation\n const params = new URLSearchParams(searchParams.toString())\n params.set(\"page\", page.toString())\n \n // Update URL without navigation (for bookmarking support)\n const newUrl = `${pathname}?${params.toString()}`\n window.history.replaceState(null, '', newUrl)\n \n // Restore scroll position after a brief delay to allow content to render\n setTimeout(() => {\n window.scrollTo({\n top: currentScrollY,\n behavior: 'instant' // Instant to prevent any scroll animation\n })\n }, 0)\n }\n\n // Don't render pagination if there's only one page\n if (totalPages <= 1) return null\n\n return (\n <div className={className}>\n <Pagination\n currentPage={currentPage}\n totalPages={totalPages}\n onPageChange={handlePageChange}\n />\n </div>\n )\n} ","\"use client\";\n\nimport { Search, FileText, Package } from \"lucide-react\"\nimport { Button } from \"./ui/button\"\nimport { useRouter } from \"../embed-shims/next-navigation\"\n\nexport interface EmptyStateProps {\n type: 'vendors' | 'posts' | 'search' | 'generic'\n title?: string\n description?: string\n showBackButton?: boolean\n onGoBack?: () => void\n backButtonText?: string\n // New CTA properties\n showCTA?: boolean\n ctaText?: string\n onCtaClick?: () => void\n ctaVariant?: 'primary' | 'secondary'\n}\n\nexport function EmptyState({\n type,\n title,\n description,\n showBackButton = false,\n onGoBack,\n backButtonText = \"Go Back\",\n showCTA = true,\n ctaText,\n onCtaClick,\n ctaVariant = 'primary'\n}: EmptyStateProps) {\n const router = useRouter()\n\n // Default content based on type\n const getDefaultContent = () => {\n switch (type) {\n case 'vendors':\n return {\n icon: <Package className=\"w-full h-full\" />,\n title: \"No vendors found\",\n description: \"We couldn't find any vendors matching your criteria. Try adjusting your filters or search terms.\"\n }\n case 'posts':\n return {\n icon: <FileText className=\"w-full h-full\" />,\n title: \"No articles found\",\n description: \"We couldn't find any articles matching your criteria. Try different categories, tags, or search terms.\"\n }\n case 'search':\n return {\n icon: <Search className=\"w-full h-full\" />,\n title: \"No results found\",\n description: \"Your search didn't return any results. Try different keywords or browse our categories.\"\n }\n default:\n return {\n icon: <Search className=\"w-full h-full\" />,\n title: \"Nothing found\",\n description: \"We couldn't find what you're looking for. Try adjusting your search or filters.\"\n }\n }\n }\n\n // Smart CTA logic based on context\n const getSmartCTA = () => {\n // If custom CTA is provided, use it\n if (ctaText && onCtaClick) {\n return {\n text: ctaText,\n action: onCtaClick\n }\n }\n\n // Check if we're on the client side\n const isClient = typeof window !== 'undefined'\n const currentPath = isClient ? window.location.pathname : ''\n\n // Smart defaults based on type and context\n switch (type) {\n case 'search':\n return {\n text: \"Reset Filters\",\n action: () => {\n if (isClient) {\n // Try to reset search by clearing URL params and refreshing\n const url = new URL(window.location.href)\n url.search = ''\n router.push(url.pathname)\n }\n }\n }\n case 'posts':\n // If we're on blog/community pages, reset blog filters\n if (currentPath.includes('/blog')) {\n return {\n text: \"Reset Filters\",\n action: () => {\n if (isClient) {\n // Reset blog search and filters by clearing URL params\n const url = new URL(window.location.href)\n url.search = ''\n router.push(url.pathname)\n }\n }\n }\n } else if (currentPath.includes('/profile')) {\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n }\n return {\n text: \"View All Posts\",\n action: () => router.push('/blog')\n }\n case 'vendors':\n // If we're in profile or other pages, direct to main content\n if (currentPath.includes('/profile')) {\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n } else if (currentPath.includes('/vendors') || currentPath.includes('/margin-increase/compare')) {\n return {\n text: \"Reset Filters\",\n action: () => {\n if (isClient) {\n // Reset vendor search and filters by clearing URL params\n const url = new URL(window.location.href)\n url.search = ''\n router.push(url.pathname)\n }\n }\n }\n }\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n default:\n return {\n text: \"Browse Vendors\",\n action: () => router.push('/vendors')\n }\n }\n }\n\n const defaultContent = getDefaultContent()\n const displayTitle = title || defaultContent.title\n const displayDescription = description || defaultContent.description\n const smartCTA = getSmartCTA()\n\n return (\n <div className=\"flex flex-col items-center justify-center py-6 md:py-16 px-6 text-center\">\n {/* Icon */}\n <div className=\"mb-3 md:mb-6 flex items-center justify-center\">\n <div className=\"rounded-full bg-ods-card p-3 md:p-6 border border-ods-border\">\n <div className=\"w-8 h-8 md:w-16 md:h-16 text-ods-text-secondary flex items-center justify-center\">\n {defaultContent.icon}\n </div>\n </div>\n </div>\n\n {/* Title */}\n <h2 className=\"mb-2 md:mb-3 text-h3 text-ods-text-primary\">\n {displayTitle}\n </h2>\n\n {/* Description */}\n <p className=\"mb-4 md:mb-8 max-w-md text-h6 text-ods-text-secondary\">\n {displayDescription}\n </p>\n\n {/* Smart CTA Button */}\n {showCTA && smartCTA && (\n <div className=\"w-full max-w-xs mb-3\">\n <Button\n onClick={smartCTA.action}\n className={ctaVariant === 'primary'\n ? \"w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-body font-medium\"\n : \"w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-body font-medium\"\n }\n >\n {smartCTA.text}\n </Button>\n </div>\n )}\n\n {/* Optional Back Button */}\n {showBackButton && onGoBack && (\n <div className=\"w-full max-w-xs\">\n <Button\n onClick={onGoBack}\n variant=\"outline\"\n className=\"w-full transition-all duration-150 font-body font-medium\"\n >\n {backButtonText}\n </Button>\n </div>\n )}\n </div>\n )\n} ","'use client';\n\n/**\n * DevSectionView — the canonical chrome for ANY dev-center section\n * (Roadmap / Delivery / Releases). One component, used in BOTH:\n *\n * - tabbed `/roadmap-and-releases` (compact title mode, no `hero`)\n * - full-page `/roadmap`, `/bug-fixes-and-enhancements`, `/releases`\n * (hero mode with icon + description + back link)\n *\n * Owns: title rendering, the inline search input, the filter pill row,\n * and the URL-param wiring that connects both. The list `children`\n * receive a clean URL contract — they read `?<paramKey>=...` via\n * `useSearchParams()` and refetch on change. No duplicated controls.\n */\n\nimport type { ReactNode } from 'react';\nimport { useState, useEffect } from 'react';\nimport { useRouter, useSearchParams, usePathname } from '../../../embed-shims';\nimport { SearchInput } from '../../ui';\nimport { StatusFilterComponent } from '../../features';\nimport {\n OPENFRAME_DEV_SECTIONS,\n type OpenframeDevSectionKey,\n} from '../../../utils/dev-sections/openframe-dev-sections';\n\nexport interface DevSectionViewProps {\n /** Which section to render — drives title, search, and filter\n * config via the `OPENFRAME_DEV_SECTIONS` registry. */\n sectionKey: OpenframeDevSectionKey;\n /** When set, renders the rich page-level hero (icon + h1 + description).\n * Omit for the compact tab-context heading. */\n hero?: {\n /** Pre-rendered icon JSX. Server components render the icon themselves\n * and pass the element here — function references can't cross the\n * server→client boundary, but React elements can. */\n icon: ReactNode;\n /** Hero title. Falls back to `OPENFRAME_DEV_SECTIONS[sectionKey].hero.title`\n * when omitted, so embedders can override the (OpenFrame-specific) default\n * copy without forking the registry. */\n title?: string;\n description: string;\n };\n /** Optional slot rendered BETWEEN the hero and the search/filter\n * controls. Use this for an entry-action surface that should sit\n * above the list (e.g. the Help Center's \"Open a new ticket\" form).\n * The slot is wrapped in the same `gap-10` flex column so spacing\n * matches the surrounding chrome — callers should NOT add their\n * own top/bottom margin. Renders `null` (no DOM) when omitted. */\n preControls?: ReactNode;\n /** The page-specific list body. Reads URL params written by this\n * component (search input + filter pills). */\n children: ReactNode;\n /** Render this component's own title heading (hero or compact `h2`). Default\n * `true` — the tab context renders the compact heading itself. Pass `false`\n * when the host already renders the title elsewhere (e.g. `DevSectionPage`\n * routes it through the unified `PageLayout` `TitleBlock`): then only the\n * search + filter controls + list render, with no duplicate heading. */\n showHeading?: boolean;\n}\n\nexport function DevSectionView({ sectionKey, hero, preControls, children, showHeading = true }: DevSectionViewProps) {\n const section = OPENFRAME_DEV_SECTIONS[sectionKey];\n const router = useRouter();\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = section.search;\n const filter = section.filter;\n\n const currentSearch = search ? searchParams.get(search.paramKey) || '' : '';\n const currentFilterValue = filter\n ? searchParams.get(filter.paramKey) || filter.defaultValue\n : '';\n\n // Controlled search-input state — input commits to the URL only on\n // Enter (not on every keystroke), preserving the legacy behavior.\n // Lazy init from URL avoids a brief flash of stale value on first\n // paint after URL-driven re-render (e.g. tab switch).\n const [searchValue, setSearchValue] = useState(() => currentSearch);\n useEffect(() => {\n setSearchValue(currentSearch);\n }, [currentSearch]);\n\n const handleSearchSubmit = (value: string) => {\n if (!search) return;\n const params = new URLSearchParams(searchParams.toString());\n if (value.trim()) params.set(search.paramKey, value.trim());\n else params.delete(search.paramKey);\n router.replace(`${pathname}?${params.toString()}`, { scroll: false });\n };\n\n const handleFilterChange = (value: string) => {\n if (!filter) return;\n const params = new URLSearchParams(searchParams.toString());\n if (value === filter.defaultValue) params.delete(filter.paramKey);\n else params.set(filter.paramKey, value);\n router.replace(`${pathname}?${params.toString()}`, { scroll: false });\n };\n\n return (\n <div className=\"w-full flex flex-col gap-10\">\n {showHeading &&\n (hero ? (\n <div className=\"space-y-4\">\n <h1 className=\"text-h1 tracking-[-1.12px] text-ods-text-primary flex items-center gap-3\">\n {hero.icon}\n {hero.title ?? section.hero.title}\n </h1>\n <p className=\"text-h4 text-ods-text-secondary max-w-3xl\">\n {hero.description}\n </p>\n </div>\n ) : (\n <div className=\"flex items-center justify-between w-full\">\n <h2 className=\"font-['Azeret_Mono'] font-semibold text-[32px] md:text-[40px] lg:text-[48px] leading-[40px] md:leading-[48px] lg:leading-[56px] text-ods-text-primary tracking-[-0.64px] md:tracking-[-0.8px] lg:tracking-[-0.96px]\">\n {section.hero.title}\n <span className=\"text-ods-accent\">:</span>\n </h2>\n </div>\n ))}\n\n {preControls}\n\n {(search || filter) && (\n <div className=\"space-y-4\">\n {search && (\n <SearchInput\n showDropdown={false}\n placeholder={search.placeholder}\n value={searchValue}\n onChange={setSearchValue}\n onSubmit={handleSearchSubmit}\n />\n )}\n {filter && (\n <StatusFilterComponent\n selectedStatus={currentFilterValue}\n onStatusChange={handleFilterChange}\n statusOptions={[...filter.options]}\n />\n )}\n </div>\n )}\n\n {children}\n </div>\n );\n}\n","'use client';\n\n/**\n * DevSectionPage — full-page wrapper for a dev-center section\n * (`/roadmap`, `/bug-fixes-and-enhancements`, `/releases`).\n *\n * Mounts the lib's canonical `PageLayout` directly (no in-app wrapper)\n * so the back-button affordance stays in lockstep with whatever the\n * design system ships — any future lib change to BackButton / TitleBlock\n * propagates automatically.\n *\n * Composition: `PageShell` → `PageLayout` (back-to-home wired) →\n * `DevSectionView` (icon hero + search + filter pills) → list body.\n *\n * Adding a new section is one entry in `OPENFRAME_DEV_SECTIONS` plus a\n * single-line page file mounting this factory with the new key.\n */\n\nimport type { ReactNode } from 'react';\nimport { useRouter } from '../../../embed-shims/next-navigation';\nimport { PageShell, PageLayout } from '../../ui';\nimport { DevSectionView } from './dev-section-view';\nimport {\n OPENFRAME_DEV_SECTIONS,\n type OpenframeDevSectionKey,\n} from '../../../utils/dev-sections/openframe-dev-sections';\n\nexport interface DevSectionPageProps {\n sectionKey: OpenframeDevSectionKey;\n /** The page-specific list body (e.g. `<RoadmapList />`). */\n children: ReactNode;\n /** Optional slot rendered BETWEEN the hero and search/filter — see\n * `DevSectionView.preControls`. Used by surfaces that want an entry\n * action (e.g. Help Center's \"Open a new ticket\" form) above the\n * controls instead of below them. */\n preControls?: ReactNode;\n /** Back-button config — same shape as `LegalDocumentPage` /\n * `ReleaseDetailPage`. Pass `false` to hide entirely. Default\n * `{ label: 'Back to home', href: '/' }` — embedders whose \"home\" isn't `/`\n * should override `href`, or pass `false` if the embed has no home page. */\n backButton?: { label?: string; href?: string } | false;\n /** Override the hero title. Defaults to the (OpenFrame-specific) copy in\n * `OPENFRAME_DEV_SECTIONS[sectionKey].hero.title`. Set this to brand the\n * section for a non-OpenFrame embed. */\n title?: string;\n /** Override the hero subtitle/description. Defaults to\n * `OPENFRAME_DEV_SECTIONS[sectionKey].hero.description`. */\n subtitle?: string;\n /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n * `true` — the contract for marketing/hub surfaces with no app shell. Pass\n * `false` when the host layout already provides the page container (e.g.\n * openframe-frontend's `AppLayout` `<main>`): then only the\n * `page-shell-content` padding box is rendered, avoiding a nested `<main>`. */\n shell?: boolean;\n}\n\nexport function DevSectionPage({\n sectionKey,\n children,\n preControls,\n backButton,\n title,\n subtitle,\n shell = true,\n}: DevSectionPageProps) {\n const router = useRouter();\n const section = OPENFRAME_DEV_SECTIONS[sectionKey];\n\n // Back-button config — mirrors LegalDocumentPage / ReleaseDetailPage.\n // Default: { label: 'Back to home', href: '/' }. Pass `false` to hide.\n // After `backButton &&` narrowing, inner type is `{ label?, href? } |\n // undefined`; don't re-compare to `false` (TS2367).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: (backButton ? backButton.label : undefined) ?? 'Back to home',\n onClick: () => router.push((backButton ? backButton.href : undefined) ?? '/'),\n };\n\n const inner = (\n // Unified header: title/description route through the canonical (frozen)\n // `PageLayout` `TitleBlock` (text-h2) — same as FAQ / Legal / detail pages —\n // so every help-center surface shares one header. `DevSectionView` then\n // renders ONLY its search + filter controls (`showHeading={false}`), no\n // duplicate title. (The hero icon is intentionally dropped: TitleBlock is\n // frozen and renders title-only.)\n <PageLayout\n title={title ?? section.hero.title}\n subtitle={subtitle ?? section.hero.description}\n titleSize=\"h1\"\n titleWrap\n backButton={backCfg}\n >\n <DevSectionView sectionKey={sectionKey} showHeading={false} preControls={preControls}>\n {children}\n </DevSectionView>\n </PageLayout>\n );\n\n // `shell` true → standalone `<PageShell>` (own <main> + bg + max-width).\n // false → padding-only box (no nested <main>) for hosts whose layout already\n // provides the container; both consume the host's `--page-shell-*` vars.\n return shell ? <PageShell>{inner}</PageShell> : <div className=\"page-shell-content\">{inner}</div>;\n}\n","'use client';\n\n/**\n * Shared row chrome for any `DevSectionPage` list (delivery, tickets,\n * future sections). One source of truth for the layout that every\n * dev-section card row uses:\n * left column → title (h3) / subtitle (h5 uppercase) / description\n * (h4 line-clamp-3), each in a fixed min-height block\n * so rows align across the grid\n * right column → caller-supplied stacked badges\n *\n * Surface stays small on purpose — `rightBadges` is a `ReactNode` so\n * the caller decides how many badges (delivery: 2, tickets: 1-2,\n * future: anything). No behavior baked in: the caller wraps the row\n * in a `<div>` (static, like delivery) or `<button>` (clickable, like\n * tickets) and renders the row content via this component.\n *\n * Pair with `DevCardRowSkeletonList` for the loading state — the\n * skeleton mirrors the same min-heights so the in-flight UI doesn't\n * shift the layout when real data lands.\n *\n * NOTE: the ticket conversation row is NOT here — it renders the shared\n * `<ChatMessageRow>` (`components/chat/chat-message-row.tsx`), the SAME\n * component the OpenMSP Slack-community feed uses, so the two surfaces stay\n * pixel-identical by construction.\n */\n\nimport type { ReactNode } from 'react';\n\nexport interface DevCardRowContentProps {\n title: string;\n /** Single-line uppercase metadata (e.g. \"UPDATED today, #4271, Code review\"). */\n subtitle: string;\n /** 3-line description block. Empty string renders the fallback. */\n description: string;\n /** Fallback copy when `description` is empty. Defaults to a generic\n * string; ticket / delivery surfaces override. */\n emptyDescription?: string;\n /** Right column — caller renders its own stacked badges. */\n rightBadges: ReactNode;\n}\n\nexport function DevCardRowContent({\n title,\n subtitle,\n description,\n emptyDescription = 'No description provided',\n rightBadges,\n}: DevCardRowContentProps) {\n return (\n <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n <div className=\"min-h-[24px] flex items-center\">\n <h3 className=\"text-h3 text-ods-text-primary tracking-[-0.36px] flex-1 line-clamp-2 md:truncate break-words\">\n {title}\n </h3>\n </div>\n <div className=\"min-h-[20px] flex items-center\">\n <p className=\"text-h5 text-ods-text-secondary uppercase tracking-[-0.28px] truncate\">\n {subtitle}\n </p>\n </div>\n <div className=\"min-h-[72px] flex items-center\">\n <p className=\"text-h4 text-ods-text-secondary line-clamp-3 break-words\">\n {description || emptyDescription}\n </p>\n </div>\n </div>\n <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n {rightBadges}\n </div>\n </div>\n );\n}\n\n/**\n * Skeleton rendering for a single row — the bars mirror the same\n * min-heights as `DevCardRowContent` so the loading→loaded swap\n * doesn't reflow.\n */\nexport function DevCardRowSkeleton() {\n return (\n <div className=\"border-b border-ods-border last:border-b-0 p-[12px] md:p-[16px]\">\n <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n <div className=\"min-h-[24px] flex items-center\">\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n </div>\n <div className=\"min-h-[20px] flex items-center\">\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-1/2\" />\n </div>\n <div className=\"min-h-[72px] flex items-center\">\n <div className=\"flex-1 space-y-1\">\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-full\" />\n <div className=\"h-[20px] bg-ods-border rounded animate-pulse w-2/3\" />\n </div>\n </div>\n </div>\n <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n <div className=\"h-[32px] w-[100px] bg-ods-border rounded animate-pulse\" />\n <div className=\"h-[32px] w-[120px] bg-ods-border rounded animate-pulse\" />\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * The standard \"5 skeleton rows inside a bordered card\" loading state\n * used by every list shell. Both delivery (`delivery-table.tsx`) and\n * tickets (`tickets-list.tsx`) mount this directly.\n */\nexport function DevCardRowSkeletonList({ rows = 5 }: { rows?: number }) {\n return (\n <div className=\"bg-ods-card border border-ods-border rounded-[6px] overflow-hidden w-full\">\n {Array.from({ length: rows }, (_, i) => (\n <DevCardRowSkeleton key={i} />\n ))}\n </div>\n );\n}\n","'use client'\n\n/**\n * `<DeliveryRow />` — canonical single-row presentation for a ClickUp\n * delivery item.\n *\n * Single source of truth: both the `/bug-fixes-and-enhancements` page\n * (via `DeliveryTable`) AND the linked-delivery card on a HubSpot ticket\n * (via `TicketLinkedDeliveryCard`) compose this primitive. Visual parity\n * across those two surfaces is the design goal — the user reads the\n * card on their ticket and recognises it as a row from the public\n * delivery list.\n *\n * Behaviors:\n * - `href` set → outer element is an `<a>`, the whole row becomes\n * clickable (used by the linked-card surface to deep-link into\n * `/bug-fixes-and-enhancements?focus=<id>`).\n * - `id` set → outer element gets that DOM id so the consuming page\n * can `scrollIntoView` to it when the URL carries `?focus=<id>`.\n * - `highlighted` true → brief accent border + background pulse\n * (`animate-flash-focus` keyframe defined in `tailwind.config.ts`).\n * - `caption` set → small uppercase label rendered above the title\n * (\"LINKED DELIVERY\" on the ticket-side variant). Omitted on the\n * standard list rendering.\n */\n\nimport * as React from 'react'\nimport Link from '../../../embed-shims/next-link'\nimport { StatusBadge } from '../../ui/status-badge'\nimport { getStatusColorScheme } from '../../../utils'\nimport {\n type DeliveryItem,\n TASK_TYPE_LABELS,\n TASK_TYPE_TEXT_COLORS,\n} from '../../../types/delivery'\nimport { cn } from '../../../utils/cn'\n\n/** Same heuristic as DeliveryTable's local helper. Inlined so the row\n * primitive owns its complete rendering contract. */\nfunction getRelativeTime(timestamp: number): string {\n const now = Date.now()\n const diff = now - timestamp\n const days = Math.floor(diff / (1000 * 60 * 60 * 24))\n const weeks = Math.floor(days / 7)\n const months = Math.floor(days / 30)\n if (months > 0) return months === 1 ? 'last month' : `${months} months ago`\n if (weeks > 0) return weeks === 1 ? 'last week' : `${weeks} weeks ago`\n if (days > 0) return days === 1 ? 'yesterday' : `${days} days ago`\n return 'today'\n}\n\nexport interface DeliveryRowProps {\n item: DeliveryItem\n /** When set, the row becomes a clickable anchor. The ticket-side\n * linked-card composes this from `buildDevSectionUrl('delivery', id)`\n * which carries `?search=<id>` — the delivery list filters to that\n * exact task on landing (canonical deep-link mechanism, same one\n * the chat-inline delivery card uses). */\n href?: string\n /** Small uppercase caption rendered above the title. Used by the\n * linked-delivery card variant (\"LINKED DELIVERY\"). */\n caption?: string\n /** DOM `id` applied to the row's outer element. `DeliveryTable`\n * always sets `delivery-<external_id>` so chat-card deep-links\n * (`?search=<id>#delivery-<id>`) and the ticket linked-card path\n * both have a target for `useScrollToHash` to scroll to. Always\n * paired with `scroll-mt-24` on the outer element so the row lands\n * BELOW the sticky chrome after the scroll. */\n id?: string\n className?: string\n}\n\nexport function DeliveryRow({\n item,\n href,\n caption,\n id,\n className,\n}: DeliveryRowProps) {\n const taskType = item.taskType as keyof typeof TASK_TYPE_LABELS\n const typeBadgeLabel = TASK_TYPE_LABELS[taskType] || 'TASK'\n const typeBadgeTextColor = TASK_TYPE_TEXT_COLORS[taskType] || ''\n const statusBadgeScheme = getStatusColorScheme(item.status)\n const relativeTime = getRelativeTime(item.dateUpdated)\n const subtitle = `ACTIVE ${relativeTime}${item.listNames.length > 0 ? `, ${item.listNames.join(', ')}` : ''}, ${item.id}`\n\n const inner = (\n <div className=\"flex flex-col md:flex-row items-start justify-between gap-[12px] md:gap-[16px] w-full\">\n {/* Left: caption (optional) + title + subtitle + description */}\n <div className=\"flex-1 min-w-0 w-full md:w-auto flex flex-col gap-[12px] md:gap-[16px]\">\n {caption && (\n <p className=\"text-h5 text-ods-text-secondary\">\n {caption}\n </p>\n )}\n <div className=\"min-h-[24px] md:min-h-[24px] flex items-center\">\n <h3 className=\"text-h3 text-ods-text-primary tracking-[-0.36px] flex-1 line-clamp-2 md:truncate break-words\">\n {item.title}\n </h3>\n </div>\n <div className=\"min-h-[20px] flex items-center\">\n <p className=\"text-h5 text-ods-text-secondary uppercase tracking-[-0.28px] truncate\">\n {subtitle}\n </p>\n </div>\n <div className=\"min-h-[72px] flex items-center\">\n <p className=\"text-h4 text-ods-text-secondary line-clamp-3 break-words\">\n {item.description || 'No description provided'}\n </p>\n </div>\n </div>\n\n {/* Right: status + task-type badges */}\n <div className=\"flex-shrink-0 self-start flex flex-col gap-2\">\n <StatusBadge\n text={item.status.toUpperCase()}\n colorScheme={statusBadgeScheme}\n variant=\"card\"\n className=\"border border-ods-border\"\n />\n <StatusBadge\n text={typeBadgeLabel}\n variant=\"card\"\n className={`border border-ods-border ${typeBadgeTextColor}`}\n />\n </div>\n </div>\n )\n\n const baseClass = cn(\n 'block p-[12px] md:p-[16px] no-underline text-inherit transition-colors duration-150',\n // `scroll-mt-24` is paid for whether `id` is set or not (it's a\n // single Tailwind utility, no runtime cost). Keeping it\n // unconditional means a future caller adding `id` doesn't also\n // have to remember to ask for the offset.\n 'scroll-mt-24',\n href && 'hover:bg-ods-bg-hover cursor-pointer',\n className,\n )\n\n if (href) {\n // `Link` is the env-aware embed-shim — delegates to `next/link` on\n // a Next.js host (soft RSC nav, back-button restores the previous\n // page's React state intact), falls back to a plain `<a>` on\n // non-Next embedders. A raw `<a href>` was hard-navigating +\n // losing TanStack-Query state on back, leaving /tickets stuck on\n // its skeleton.\n return (\n <Link href={href} id={id} className={baseClass} prefetch={false}>\n {inner}\n </Link>\n )\n }\n\n return <div id={id} className={baseClass}>{inner}</div>\n}\n"]}
|
|
@@ -151,16 +151,7 @@ var init_cn = _chunkVRHGVLSLcjs.__esm.call(void 0, {
|
|
|
151
151
|
twMerge = _tailwindmerge.extendTailwindMerge.call(void 0, {
|
|
152
152
|
extend: {
|
|
153
153
|
classGroups: {
|
|
154
|
-
"ods-typography": [
|
|
155
|
-
"text-h1",
|
|
156
|
-
"text-h2",
|
|
157
|
-
"text-h3",
|
|
158
|
-
"text-h4",
|
|
159
|
-
"text-h5",
|
|
160
|
-
"text-h6",
|
|
161
|
-
"text-code",
|
|
162
|
-
"text-badge"
|
|
163
|
-
]
|
|
154
|
+
"ods-typography": ["text-h1", "text-h2", "text-h3", "text-h4", "text-h5", "text-h6"]
|
|
164
155
|
}
|
|
165
156
|
}
|
|
166
157
|
});
|
|
@@ -175,4 +166,4 @@ var init_cn = _chunkVRHGVLSLcjs.__esm.call(void 0, {
|
|
|
175
166
|
|
|
176
167
|
|
|
177
168
|
exports.getPlatformProductionUrl = getPlatformProductionUrl; exports.getAllPlatformBaseDomains = getAllPlatformBaseDomains; exports.init_platform_domains = init_platform_domains; exports.cn = cn; exports.getBaseUrl = getBaseUrl; exports.init_cn = init_cn;
|
|
178
|
-
//# sourceMappingURL=chunk-
|
|
169
|
+
//# sourceMappingURL=chunk-FIG2RKZF.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-FIG2RKZF.cjs","../src/platform-domains.ts","../src/utils/cn.ts"],"names":[],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACA;AC4EO,SAAS,KAAA,CAAM,GAAA,EAA8C;AAClE,EAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,IAAA,IAAQ,GAAG,CAAA;AACnD;AAGA,SAAS,cAAA,CAAe,GAAA,EAA4B;AAClD,EAAA,MAAM,OAAA,kBAAS,KAAA,iBAAM,GAAG,CAAA,6BAAG,QAAA;AAC3B,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAO,IAAA;AACpB,EAAA,OAAQ,aAAA,CAAqD,MAAM,EAAA,GAAK,IAAA;AAC1E;AAiBO,SAAS,YAAA,CAAa,GAAA,EAAqB;AAChD,EAAA,MAAM,QAAA,EAAU,GAAA,CAAI,IAAA,CAAK,CAAA;AACzB,EAAA,GAAA,CAAI,0BAAA,CAA2B,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,OAAA;AACrD,EAAA,OAAO,CAAA,QAAA,EAAW,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAA;AAC/C;AASmE;AAG/D,EAAA;AAG0B,EAAA;AAC9B;AAKwE;AACnD,EAAA;AACf,EAAA;AACyC,IAAA;AACrC,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAGsD;AACT,EAAA;AACD,EAAA;AAC9B,EAAA;AACd;AAG0E;AAC5C,EAAA;AACkB,EAAA;AACvC,EAAA;AACT;AAGoD;AACZ,EAAA;AACxC;AAGgG;AACtE,EAAA;AACe,EAAA;AACA,EAAA;AAChC,EAAA;AACT;AAOkF;AAC9C,EAAA;AACI,EAAA;AAClB,IAAA;AACyB,IAAA;AAC7C,EAAA;AACO,EAAA;AACT;AAWyD;AACjB,EAAA;AACxC;AAgBsD;AACT,EAAA;AAEV,EAAA;AAGY,EAAA;AACnC,IAAA;AACV,EAAA;AAI6B,EAAA;AAGb,EAAA;AACqB,IAAA;AACrC,EAAA;AAGoC,EAAA;AACE,EAAA;AAClB,IAAA;AACE,IAAA;AACT,IAAA;AAC8B,IAAA;AAC/B,IAAA;AACkB,MAAA;AACN,MAAA;AACtB,IAAA;AACF,EAAA;AAC6B,EAAA;AAC/B;AA6CM;AA9RN;AAAA,EAAA;AAAA,IAAA;AAkCgE,IAAA;AAC1B,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAuE;AAAA;AAAA;AAAA;AAKpE,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACG,MAAA;AACH,MAAA;AACzC,IAAA;AAgBsB,IAAA;AACmB,MAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACD,MAAA;AACH,MAAA;AACC,MAAA;AACJ,MAAA;AACK,MAAA;AACF,MAAA;AACvC,IAAA;AAgN6D,IAAA;AAEzB,IAAA;AAM1B,MAAA;AACN,QAAA;AAEF,MAAA;AACF,IAAA;AAAA,EAAA;AAAA;AD5KgD;AACA;AE/HV;AACF;AAkBQ;AACf,EAAA;AAC7B;AAsBsD;AAET,EAAA;AACC,IAAA;AAC5C,EAAA;AAGc,EAAA;AAC4B,IAAA;AAC1C,EAAA;AAGgB,EAAA;AACgB,IAAA;AAChC,EAAA;AAK4C,EAAA;AAC9C;AAvDM;AARN;AAAA,EAAA;AAAA,IAAA;AAMA,IAAA;AAEsD,IAAA;AAC5C,MAAA;AACO,QAAA;AACmB,UAAA;AAChC,QAAA;AACF,MAAA;AACD,IAAA;AAAA,EAAA;AAAA;AFiJ+C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-FIG2RKZF.cjs","sourcesContent":[null,"/**\n * Platform-Domain SSOT (single source of truth) + derivations.\n *\n * ONE registry maps each platform → its canonical production URL (`defaultUrl`,\n * the load-bearing source) with an optional per-deploy `NEXT_PUBLIC_*_URL`\n * OVERRIDE. Everything else — the reverse host→platform resolver, the cookie\n * base-domain set (the cross-subdomain SSO mechanism), www/apex expansion, the\n * URL→host parse, preview detection — derives from this one table.\n *\n * EDGE-SAFE + PURE: no React/clsx/tailwind, no `server-only`, no `node:`\n * builtins, no `'use client'`. So it is legal in the Edge middleware\n * (`proxy.ts`), in `'use client'` providers, AND in `server-only` modules\n * (e.g. cookie-domain-server.ts) simultaneously. The ONLY non-pure export is\n * `getAllPlatformBaseDomains`, which reads `typeof window`/`process.env` to\n * preserve byte-identical cookie behavior.\n */\n\nimport type { PlatformName } from './types/platform'\n\nexport type PlatformDomainKey = PlatformName | 'openframe-dashboard'\n\nexport interface PlatformDomainEntry {\n /** Platform key (matches `PlatformName`, plus the forward-only `openframe-dashboard`). */\n key: PlatformDomainKey\n /** Canonical production URL — the LOAD-BEARING source of truth (today's hardcoded fallbacks). */\n defaultUrl: string\n /** `NEXT_PUBLIC_*_URL` per-deploy OVERRIDE. The `defaultUrl` covers it when unset. */\n envVar: string\n /** Legacy/secondary hosts that REVERSE-map to this key (no env var exists — NOT canonical). */\n aliasHostnames?: string[]\n /** Forward-only: no DB row, excluded from the reverse index + cookie set (e.g. the product-CTA dashboard). */\n pseudo?: boolean\n}\n\nexport const PLATFORM_DOMAINS: readonly PlatformDomainEntry[] = [\n { key: 'marketing-hub', defaultUrl: 'https://marketing-hub.flamingo.so', envVar: 'NEXT_PUBLIC_MARKETING_HUB_URL' },\n { key: 'company-hub', defaultUrl: 'https://company-hub.flamingo.so', envVar: 'NEXT_PUBLIC_COMPANY_HUB_URL' },\n { key: 'product-hub', defaultUrl: 'https://product-hub.flamingo.so', envVar: 'NEXT_PUBLIC_PRODUCT_HUB_URL' },\n { key: 'revenue-hub', defaultUrl: 'https://revenue-hub.flamingo.so', envVar: 'NEXT_PUBLIC_REVENUE_HUB_URL' },\n { key: 'people-hub', defaultUrl: 'https://people-hub.flamingo.so', envVar: 'NEXT_PUBLIC_PEOPLE_HUB_URL' },\n { key: 'openmsp', defaultUrl: 'https://www.openmsp.ai', envVar: 'NEXT_PUBLIC_OPENMSP_URL' },\n // ORDERING INVARIANT (first-wins, load-bearing): `flamingo` MUST precede `flamingo-teaser` + `universal`.\n // All three resolve to www.flamingo.run; the reverse index is first-wins → flamingo claims the shared host,\n // teaser keeps only its unique flamingo.cx aliases, universal contributes no unique host.\n // ⚠️ DO NOT REORDER — enforced by the module-load self-check at the bottom of this file.\n { key: 'flamingo', defaultUrl: 'https://www.flamingo.run', envVar: 'NEXT_PUBLIC_FLAMINGO_URL' },\n { key: 'tmcg', defaultUrl: 'https://www.tmcg.miami', envVar: 'NEXT_PUBLIC_TMCG_URL' },\n { key: 'flamingo-teaser', defaultUrl: 'https://www.flamingo.run', envVar: 'NEXT_PUBLIC_FLAMINGO_URL', aliasHostnames: ['flamingo.cx', 'www.flamingo.cx'] },\n { key: 'openframe', defaultUrl: 'https://openframe.ai', envVar: 'NEXT_PUBLIC_OPENFRAME_URL', aliasHostnames: ['openframe.ai', 'www.openframe.ai', 'hub.openframe.ai'] },\n { key: 'openframe-dashboard', defaultUrl: 'https://openframe.ai', envVar: 'NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL', pseudo: true },\n { key: 'universal', defaultUrl: 'https://www.flamingo.run', envVar: 'NEXT_PUBLIC_FLAMINGO_URL' },\n]\n\n// ── Compile-time key guards (anchor the table on PlatformName, both directions) ──\n// (1) Removal/typo guard: every table key must be a valid PlatformDomainKey.\nconst _tableSatisfies = PLATFORM_DOMAINS satisfies readonly PlatformDomainEntry[]\nvoid _tableSatisfies\n// (2) Addition guard: a NEW PlatformName member that lacks a table row fails the build.\ntype _MissingKey = Exclude<PlatformName, (typeof PLATFORM_DOMAINS)[number]['key']>\nconst _exhaustive: [_MissingKey] extends [never] ? true : false = true\nvoid _exhaustive\n\n// ── Env overrides (the ONLY place env URLs enter — literal-key inlined + compile-time-guarded) ──\n// `process.env.NEXT_PUBLIC_X` is build-inlined ONLY with a LITERAL key, so the env-var name is the\n// irreducible two-copy (the registry `envVar` column + the literal access below). The `satisfies` makes\n// `tsc`/`next build` FAIL if this map is missing a registry env var OR carries a stale one (bidirectional).\ntype EnvVarKey = (typeof PLATFORM_DOMAINS)[number]['envVar']\nconst ENV_OVERRIDES = {\n NEXT_PUBLIC_MARKETING_HUB_URL: process.env.NEXT_PUBLIC_MARKETING_HUB_URL,\n NEXT_PUBLIC_COMPANY_HUB_URL: process.env.NEXT_PUBLIC_COMPANY_HUB_URL,\n NEXT_PUBLIC_PRODUCT_HUB_URL: process.env.NEXT_PUBLIC_PRODUCT_HUB_URL,\n NEXT_PUBLIC_REVENUE_HUB_URL: process.env.NEXT_PUBLIC_REVENUE_HUB_URL,\n NEXT_PUBLIC_PEOPLE_HUB_URL: process.env.NEXT_PUBLIC_PEOPLE_HUB_URL,\n NEXT_PUBLIC_OPENMSP_URL: process.env.NEXT_PUBLIC_OPENMSP_URL,\n NEXT_PUBLIC_FLAMINGO_URL: process.env.NEXT_PUBLIC_FLAMINGO_URL,\n NEXT_PUBLIC_TMCG_URL: process.env.NEXT_PUBLIC_TMCG_URL,\n NEXT_PUBLIC_OPENFRAME_URL: process.env.NEXT_PUBLIC_OPENFRAME_URL,\n NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL: process.env.NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL,\n} satisfies Record<EnvVarKey, string | undefined>\n\n/** The registry entry for a key (undefined for an unknown key). */\nexport function byKey(key: string): PlatformDomainEntry | undefined {\n return PLATFORM_DOMAINS.find((e) => e.key === key)\n}\n\n/** Read a platform's `NEXT_PUBLIC_*_URL` override (or null). */\nfunction envOverrideFor(key: string): string | null {\n const envVar = byKey(key)?.envVar\n if (!envVar) return null\n return (ENV_OVERRIDES as Record<string, string | undefined>)[envVar] || null\n}\n\n/**\n * Ensure a URL string carries a scheme. Per-deploy `NEXT_PUBLIC_*_URL` overrides are\n * stored SCHEME-LESS (bare host, e.g. `www.openmsp.ai` / `hub.openframe.ai`) — the\n * canonical convention in the Vercel shared-env store. This normalizes them to a full\n * `https://` URL so every downstream consumer (`hostOf`/`new URL`, hrefs, the cookie\n * base-domain derivation, CSP) receives a parseable URL. Full-URL inputs (the registry\n * `defaultUrl`s, any scheme'd override) pass through unchanged.\n *\n * EXPORTED as the single owner of the scheme-normalization rule (next.config.mjs keeps a\n * byte-identical local copy ONLY because Next evaluates its config outside the TS module\n * graph and cannot import this — see the comment there).\n *\n * Handles a (theoretical) protocol-relative `//host` too: strips the leading slashes so it\n * doesn't become `https:////host` (empty-host → hostOf null → silent platform drop).\n */\nexport function ensureScheme(url: string): string {\n const trimmed = url.trim()\n if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed)) return trimmed // already has a scheme\n return `https://${trimmed.replace(/^\\/+/, '')}` // bare host or protocol-relative `//host`\n}\n\n/**\n * Canonical production URL for a platform: env override wins, else the `defaultUrl`.\n * NEVER throws / undefined — the default guarantees a host (this is what keeps the\n * cookie base-domains, the reverse map, and CSP intact even with every override unset).\n * The result ALWAYS carries a scheme (`ensureScheme`), so the scheme-less env overrides\n * resolve to valid URLs. Unknown-key fallback preserves cn.ts's flamingo.run default.\n */\nexport function getPlatformProductionUrl(platform: string): string {\n const resolved =\n envOverrideFor(platform) ??\n byKey(platform)?.defaultUrl ??\n envOverrideFor('flamingo') ??\n 'https://www.flamingo.run'\n return ensureScheme(resolved)\n}\n\n// ── Single-owner host primitives ──\n\n/** Canonical URL→host parser: `.hostname` (PORT-STRIPPED, lowercased), null on parse failure. */\nexport function hostOf(value: string | null | undefined): string | null {\n if (!value) return null\n try {\n return new URL(value).hostname.toLowerCase()\n } catch {\n return null\n }\n}\n\n/** Expand a host into its `www.`/apex pair. 3+-label and single-label hosts return `[host]`. */\nexport function expandWwwApex(host: string): string[] {\n if (host.startsWith('www.')) return [host, host.slice(4)]\n if (host.split('.').length === 2) return [host, `www.${host}`]\n return [host]\n}\n\n/** Registrable base domain (`parts.slice(-2).join('.')`), dotless; undefined for <2-label. */\nexport function toRegistrableBaseDomain(host: string): string | undefined {\n const parts = host.split('.')\n if (parts.length >= 2) return parts.slice(-2).join('.')\n return undefined\n}\n\n/** An entry's alias hosts (single-owner reader). */\nexport function aliasHostsOf(key: string): string[] {\n return byKey(key)?.aliasHostnames ?? []\n}\n\n/** All hosts an entry contributes to the reverse index (resolved host + optional aliases). */\nfunction hostsForEntry(entry: PlatformDomainEntry, opts: { includeAliases: boolean }): string[] {\n const resolved = hostOf(getPlatformProductionUrl(entry.key))\n const hosts = resolved ? expandWwwApex(resolved) : []\n if (opts.includeAliases) hosts.push(...aliasHostsOf(entry.key))\n return hosts\n}\n\n/**\n * Reverse resolver: hostname → platform key (first-wins over registry order, non-pseudo only).\n * Guarantees openframe.ai / www.openframe.ai / hub.openframe.ai → openframe in every env.\n * Replaces the hub `PLATFORM_DOMAIN_MAP`.\n */\nexport function getPlatformByHostname(hostname: string): PlatformDomainKey | null {\n const host = hostname.toLowerCase()\n for (const entry of PLATFORM_DOMAINS) {\n if (entry.pseudo) continue\n if (hostsForEntry(entry, { includeAliases: true }).includes(host)) return entry.key\n }\n return null\n}\n\n// ── Preview detection (two distinct predicates — env-form vs host-form) ──\n\n/** Env-form preview predicate (Vercel `VERCEL_ENV`). */\nexport function isPreviewEnv(): boolean {\n return process.env.VERCEL_ENV === 'preview'\n}\n\n/** Host-form preview predicate (a `*.vercel.app` host). Dot-bounded suffix so a\n * malicious `foo.vercel.app.evil.com` is NOT treated as preview. */\nexport function isPreviewHost(hostname: string): boolean {\n return hostname.endsWith('.vercel.app')\n}\n\n/**\n * ALL unique cookie base domains (the cross-subdomain SSO mechanism).\n *\n * NON-PURE (the sole such export): reads `typeof window` + `process.env` to\n * preserve byte-identical cookie behavior. Keeps the original three branches:\n * 1. localhost / private IP → [] (host-only cookies)\n * 2. Vercel preview (env OR host) → ['.vercel.app','vercel.app']\n * 3. production → for each non-pseudo platform, registrable base of its resolved\n * host, emitted as both `.base` and bare `base`.\n *\n * Because `getPlatformProductionUrl` always yields a host (override OR default),\n * `.flamingo.so` / `.flamingo.run` / `.openmsp.ai` / `.tmcg.miami` / `.openframe.ai`\n * are ALWAYS present → cross-hub SSO is byte-identical to today.\n */\nexport function getAllPlatformBaseDomains(): string[] {\n if (typeof window === 'undefined') return []\n\n const hostname = window.location.hostname\n\n // Case 1: localhost / private IP — no domains\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('127.')) {\n return []\n }\n\n // Case 2: Vercel preview — vercel.app domain\n const previewEnv =\n process.env.VERCEL_ENV === 'preview' ||\n process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' ||\n isPreviewHost(hostname)\n if (previewEnv) {\n return ['.vercel.app', 'vercel.app']\n }\n\n // Case 3: production — registrable base of every non-pseudo platform's resolved host\n const baseDomains = new Set<string>()\n for (const entry of PLATFORM_DOMAINS) {\n if (entry.pseudo) continue\n const host = hostOf(getPlatformProductionUrl(entry.key))\n if (!host) continue\n const base = toRegistrableBaseDomain(host)\n if (base) {\n baseDomains.add(`.${base}`)\n baseDomains.add(base)\n }\n }\n return Array.from(baseDomains)\n}\n\n// ── Cookie-domain guard + match (single owner; shared by the client cookie-domain.ts +\n// server-only cookie-domain-server.ts resolvers, which previously hand-rolled both 3×) ──\n\n/**\n * Hosts that must NOT receive a `Domain=` cookie → the caller returns undefined (host-only):\n * localhost, loopback/private IPs, and any `*.vercel.app`. `vercel.app` is on the Public Suffix\n * List, so browsers SILENTLY drop `Set-Cookie: Domain=.vercel.app` — which broke the PKCE verifier\n * + session cookies on preview deploys. Host-only is sufficient there (the same preview host\n * round-trips the OAuth chain); production hosts (`.flamingo.so`/`.openmsp.ai`/…) fall through.\n */\nexport function isNonCookieableHost(hostname: string): boolean {\n return (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname.startsWith('127.') ||\n hostname.startsWith('192.168.') ||\n hostname.startsWith('10.') ||\n hostname.includes('.vercel.app')\n )\n}\n\n/**\n * Match a hostname against a set of registrable base domains → the dotted cookie `Domain`\n * (`.flamingo.so`), or undefined when none contains the host. Accepts bases with or without a\n * leading dot and always returns the dotted form. Single owner for the match loop both resolvers ran.\n */\nexport function matchCookieDomain(hostname: string, baseDomains: string[]): string | undefined {\n for (const domain of baseDomains) {\n const bare = domain.startsWith('.') ? domain.slice(1) : domain\n if (hostname === bare || hostname.endsWith(`.${bare}`)) {\n return domain.startsWith('.') ? domain : `.${domain}`\n }\n }\n return undefined\n}\n\n// ── Module-load ordering self-check (NON-fatal — this module is imported by cn.ts → ~everything,\n// so a hard throw would be a total outage if the assertion were ever over-strict). Checks the REAL\n// invariant — table order — STRUCTURALLY (findIndex), NOT via getPlatformByHostname on a hardcoded\n// host: the latter false-positives when NEXT_PUBLIC_FLAMINGO_URL is overridden to a non-default host\n// (flamingo's resolved host changes, so `www.flamingo.run` no longer reverse-maps to it though the\n// ordering is fine). The alias checks below ARE env-immune (aliasHostnames are unique per key). The\n// authoritative guard is the reverse-map vitest. ⚠️ keep `flamingo` before `flamingo-teaser`/`universal`.\nconst _orderIdx = (k: PlatformDomainKey) => PLATFORM_DOMAINS.findIndex((e) => e.key === k)\nif (\n _orderIdx('flamingo') > _orderIdx('flamingo-teaser') ||\n _orderIdx('flamingo') > _orderIdx('universal') ||\n getPlatformByHostname('flamingo.cx') !== 'flamingo-teaser' ||\n getPlatformByHostname('hub.openframe.ai') !== 'openframe'\n) {\n // eslint-disable-next-line no-console\n console.error(\n '[platform-domains] ⚠️ PLATFORM_DOMAINS ordering invariant violated — `flamingo` must precede ' +\n '`flamingo-teaser`/`universal`, and the openframe/teaser aliases must be intact. Do not reorder the table.',\n )\n}\n","import { clsx, type ClassValue } from \"clsx\"\nimport { extendTailwindMerge } from \"tailwind-merge\"\n// Platform→domain resolution moved to the SSOT module `src/platform-domains.ts`.\n// `getPlatformProductionUrl` / `getAllPlatformBaseDomains` now live there (re-exported\n// via the utils barrel for existing callers); `getBaseUrl` stays here because it owns the\n// dev-localhost + Vercel-self-origin branches, and delegates its platform branch.\nimport { getPlatformProductionUrl } from \"../platform-domains\"\n\nconst twMerge = extendTailwindMerge<'ods-typography'>({\n extend: {\n classGroups: {\n 'ods-typography': ['text-h1', 'text-h2', 'text-h3', 'text-h4', 'text-h5', 'text-h6'],\n },\n },\n})\n\n/**\n * Combine class names with Tailwind's merge utility\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\n/**\n * Get the application base URL for the current environment\n *\n * @param platform - Optional platform name (openmsp, flamingo, tmcg, openframe, etc.)\n * @returns The base URL with protocol (https:// or http://)\n *\n * Priority order:\n * 1. Development (http://localhost:3000)\n * 2. Platform-specific URL via the SSOT (if `platform` provided) — env override ?? default\n * 3. VERCEL_PROJECT_PRODUCTION_URL (Vercel production domain)\n * 4. Production fallback: the DEPLOYING platform's canonical URL (NEXT_PUBLIC_APP_TYPE),\n * openmsp if unset — sourced from the SSOT, no hardcoded literal.\n *\n * The per-platform canonical URLs + their `NEXT_PUBLIC_*_URL` overrides are the single\n * source of truth in `src/platform-domains.ts` (`PLATFORM_DOMAINS`).\n *\n * @example\n * getBaseUrl() // Current deployment's URL\n * getBaseUrl('flamingo') // https://www.flamingo.run (prod) or http://localhost:3000 (dev)\n */\nexport function getBaseUrl(platform?: string): string {\n // In development, always use localhost (regardless of platform)\n if (process.env.NODE_ENV !== 'production') {\n return process.env.NEXT_PUBLIC_DEV_URL || 'http://localhost:3000'\n }\n\n // If platform is specified, return its production URL (env override ?? default)\n if (platform) {\n return getPlatformProductionUrl(platform)\n }\n\n // Production: Use Vercel domain if available\n if (process.env.VERCEL_PROJECT_PRODUCTION_URL) {\n return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`\n }\n\n // Production fallback: the deploying platform's canonical www domain (avoids Google\n // \"Page with redirect\"). Derived from the SSOT for the current app type (openmsp when\n // unset → 'https://www.openmsp.ai', byte-identical to the old hardcoded fallback).\n return getPlatformProductionUrl(process.env.NEXT_PUBLIC_APP_TYPE || 'openmsp')\n}\n"]}
|
|
@@ -7,7 +7,7 @@ var _chunkXQFFGR6Ucjs = require('./chunk-XQFFGR6U.cjs');
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _chunkFIG2RKZFcjs = require('./chunk-FIG2RKZF.cjs');
|
|
11
11
|
|
|
12
12
|
// src/components/icons/openmsp-logo.tsx
|
|
13
13
|
var _jsxruntime = require('react/jsx-runtime');
|
|
@@ -1609,7 +1609,7 @@ function EmptyVendorIcon({ className }) {
|
|
|
1609
1609
|
}
|
|
1610
1610
|
|
|
1611
1611
|
// src/components/icons/error-icon.tsx
|
|
1612
|
-
|
|
1612
|
+
_chunkFIG2RKZFcjs.init_cn.call(void 0, );
|
|
1613
1613
|
|
|
1614
1614
|
function ErrorIcon({ className, fill = "#FF6B6B", ...props }) {
|
|
1615
1615
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
@@ -1622,7 +1622,7 @@ function ErrorIcon({ className, fill = "#FF6B6B", ...props }) {
|
|
|
1622
1622
|
strokeWidth: "2",
|
|
1623
1623
|
strokeLinecap: "round",
|
|
1624
1624
|
strokeLinejoin: "round",
|
|
1625
|
-
className:
|
|
1625
|
+
className: _chunkFIG2RKZFcjs.cn.call(void 0, "inline-block", className),
|
|
1626
1626
|
...props,
|
|
1627
1627
|
children: [
|
|
1628
1628
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "circle", { cx: "12", cy: "12", r: "10" }),
|
|
@@ -2038,7 +2038,7 @@ function XLogo({ className = "", color = "white", ...props }) {
|
|
|
2038
2038
|
}
|
|
2039
2039
|
|
|
2040
2040
|
// src/components/icons/hubspot-icon.tsx
|
|
2041
|
-
|
|
2041
|
+
_chunkFIG2RKZFcjs.init_cn.call(void 0, );
|
|
2042
2042
|
|
|
2043
2043
|
function HubspotIcon({ className, fill = "#FF7A59", ...props }) {
|
|
2044
2044
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
@@ -2047,7 +2047,7 @@ function HubspotIcon({ className, fill = "#FF7A59", ...props }) {
|
|
|
2047
2047
|
xmlns: "http://www.w3.org/2000/svg",
|
|
2048
2048
|
viewBox: "0 0 512 512",
|
|
2049
2049
|
fill,
|
|
2050
|
-
className:
|
|
2050
|
+
className: _chunkFIG2RKZFcjs.cn.call(void 0, "inline-block", className),
|
|
2051
2051
|
...props,
|
|
2052
2052
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M266.197 216.109c-22.551 21.293-36.655 51.48-36.655 84.991 0 26.326 8.714 50.582 23.359 70.08l-44.473 44.74c-3.953-1.438-8.176-2.245-12.579-2.245-9.702 0-18.776 3.774-25.605 10.602-6.828 6.827-10.602 15.989-10.602 25.696 0 9.701 3.773 18.775 10.602 25.605 6.829 6.826 15.993 10.42 25.605 10.42 9.703 0 18.777-3.505 25.695-10.42 6.829-6.83 10.602-15.994 10.602-25.605 0-3.774-.538-7.369-1.707-10.873l44.923-45.102c19.765 15.183 44.381 24.169 71.244 24.169 64.599 0 116.797-52.38 116.797-116.977 0-58.578-42.854-107.093-99.007-115.628v-55.343c15.723-6.65 25.335-21.384 25.335-38.545 0-23.449-18.777-43.034-42.227-43.034-23.448 0-41.956 19.585-41.956 43.034 0 17.161 9.613 31.895 25.335 38.545v54.983c-13.655 1.887-26.593 6.019-38.362 12.219-24.796-18.778-105.565-76.997-151.746-112.126 1.078-3.953 1.798-8.085 1.798-12.397 0-25.875-21.113-46.898-47.078-46.898-25.875 0-46.898 21.023-46.898 46.898 0 25.965 21.023 46.988 46.898 46.988 8.805 0 16.98-2.606 24.078-6.828L266.197 216.109zM346.606 363.095c-34.229 0-61.991-27.763-61.991-61.994 0-34.229 27.762-61.99 61.991-61.99 34.23 0 61.992 27.761 61.992 61.99 0 34.231-27.762 61.994-61.992 61.994z" })
|
|
2053
2053
|
}
|
|
@@ -3778,7 +3778,7 @@ function LinuxIcon({ className, color = "white", ...props }) {
|
|
|
3778
3778
|
}
|
|
3779
3779
|
|
|
3780
3780
|
// src/components/icons/double-chevron-icon.tsx
|
|
3781
|
-
|
|
3781
|
+
_chunkFIG2RKZFcjs.init_cn.call(void 0, );
|
|
3782
3782
|
|
|
3783
3783
|
var rotationMap = {
|
|
3784
3784
|
left: "rotate-0",
|
|
@@ -3800,7 +3800,7 @@ function DoubleChevronIcon({
|
|
|
3800
3800
|
viewBox: "0 0 24 24",
|
|
3801
3801
|
fill: "none",
|
|
3802
3802
|
xmlns: "http://www.w3.org/2000/svg",
|
|
3803
|
-
className:
|
|
3803
|
+
className: _chunkFIG2RKZFcjs.cn.call(void 0,
|
|
3804
3804
|
"transition-transform duration-200",
|
|
3805
3805
|
rotationMap[direction],
|
|
3806
3806
|
className
|
|
@@ -4750,4 +4750,4 @@ var PilotIcon = ({ className = "w-6 h-6", color = "var(--ods-system-greys-white)
|
|
|
4750
4750
|
|
|
4751
4751
|
|
|
4752
4752
|
exports.OpenmspLogo = OpenmspLogo; exports.OpenFrameLogo = OpenFrameLogo; exports.OpenFrameText = OpenFrameText; exports.FLAMINGO_LOGO_SVG_PATH = FLAMINGO_LOGO_SVG_PATH; exports.FLAMINGO_LOGO_VIEWBOX = FLAMINGO_LOGO_VIEWBOX; exports.FlamingoLogo = FlamingoLogo; exports.ElestioLogo = ElestioLogo; exports.MlgLogo = MlgLogo; exports.MiamiCyberGangLogo = MiamiCyberGangLogo; exports.MiamiCyberGangLogoFaceOnly = MiamiCyberGangLogoFaceOnly; exports.VendorsIcon = VendorsIcon; exports.CompareIcon = CompareIcon; exports.CommunityIcon = CommunityIcon; exports.VendorDirectoryIcon = VendorDirectoryIcon; exports.OpenSourceIcon = OpenSourceIcon; exports.CommunityHubIcon = CommunityHubIcon; exports.AboutIcon = AboutIcon; exports.PlayFilledIcon = PlayFilledIcon; exports.VolumeOffFilledIcon = VolumeOffFilledIcon; exports.UserIcon = UserIcon; exports.HamburgerIcon = HamburgerIcon; exports.MenuIcon = MenuIcon; exports.ExploreCategoriesIcon = ExploreCategoriesIcon; exports.FilterIcon = FilterIcon; exports.SearchIcon = SearchIcon; exports.ScriptIcon = ScriptIcon; exports.RemoteControlIcon = RemoteControlIcon; exports.ShellIcon = ShellIcon; exports.MingoIcon = MingoIcon; exports.PoliciesIcon = PoliciesIcon; exports.QueriesIcon = QueriesIcon; exports.DocumentIcon = DocumentIcon; exports.SlidersIcon = SlidersIcon; exports.CheckCircleIcon = CheckCircleIcon; exports.PlusCircleIcon = PlusCircleIcon; exports.MinusCircleIcon = MinusCircleIcon; exports.InfoCircleIcon = InfoCircleIcon; exports.ThumbsUpIcon = ThumbsUpIcon; exports.ThumbsDownIcon = ThumbsDownIcon; exports.SunIcon = SunIcon; exports.MoonIcon = MoonIcon; exports.CoinsIcon = CoinsIcon; exports.DonutIcon = DonutIcon; exports.HandDollarIcon = HandDollarIcon; exports.SendIcon = SendIcon; exports.EditProfileIcon = EditProfileIcon; exports.EmptyVendorIcon = EmptyVendorIcon; exports.ErrorIcon = ErrorIcon; exports.MarginCrisisIcon = MarginCrisisIcon; exports.CopyIcon = CopyIcon; exports.CutVendorCostsIcon = CutVendorCostsIcon; exports.AutomateEverythingIcon = AutomateEverythingIcon; exports.ReclaimProfitsIcon = ReclaimProfitsIcon; exports.GitHubIcon = GitHubIcon; exports.SlackIcon = SlackIcon; exports.ClickUpIcon = ClickUpIcon; exports.MicrosoftIcon = MicrosoftIcon; exports.GoogleLogo = GoogleLogo; exports.RedditIcon = RedditIcon; exports.XLogo = XLogo; exports.HubspotIcon = HubspotIcon; exports.ClaudeIcon = ClaudeIcon; exports.LinkedInIcon = LinkedInIcon; exports.LumaIcon = LumaIcon; exports.LivestormIcon = LivestormIcon; exports.PodbeanIcon = PodbeanIcon; exports.WhatsAppIcon = WhatsAppIcon; exports.FigmaIcon = FigmaIcon; exports.ImageIcon = ImageIcon; exports.OpenAiIcon = OpenAiIcon; exports.GoogleGeminiIcon = GoogleGeminiIcon; exports.GlobeIcon = GlobeIcon; exports.MessageCircleIcon = MessageCircleIcon; exports.TelegramIcon = TelegramIcon; exports.YouTubeIcon = YouTubeIcon; exports.InstagramIcon = InstagramIcon; exports.FacebookIcon = FacebookIcon; exports.ArchiveIcon = ArchiveIcon; exports.CpuIcon = CpuIcon; exports.NetworkIcon = NetworkIcon; exports.ShieldIcon = ShieldIcon; exports.FileCheckIcon = FileCheckIcon; exports.BotIcon = BotIcon; exports.UsersIcon = UsersIcon; exports.PackageIcon = PackageIcon; exports.AlertTriangleIcon = AlertTriangleIcon; exports.FileTextIcon = FileTextIcon; exports.SSOConfigurationIcon = SSOConfigurationIcon; exports.XIcon = XIcon; exports.CustomStarIcon = CustomStarIcon; exports.CustomForkIcon = CustomForkIcon; exports.CustomLicenseIcon = CustomLicenseIcon; exports.CustomTimeIcon = CustomTimeIcon; exports.CustomExternalLinkIcon = CustomExternalLinkIcon; exports.VendorShowcaseMainIcon = VendorShowcaseMainIcon; exports.TransparentTrustedIcon = TransparentTrustedIcon; exports.ModularHellIcon = ModularHellIcon; exports.RapidInnovationIcon = RapidInnovationIcon; exports.LowerTCOIcon = LowerTCOIcon; exports.AiRobotIcon = AiRobotIcon; exports.SparklesIcon = SparklesIcon; exports.NaturalLanguageIcon = NaturalLanguageIcon; exports.AutomatedDiagnosticsIcon = AutomatedDiagnosticsIcon; exports.StatusUpdatesIcon = StatusUpdatesIcon; exports.SmartEscalationIcon = SmartEscalationIcon; exports.ScriptGenerationIcon = ScriptGenerationIcon; exports.PatchPolicyIcon = PatchPolicyIcon; exports.BulkOperationsIcon = BulkOperationsIcon; exports.AuditLoggingIcon = AuditLoggingIcon; exports.SOCIcon = SOCIcon; exports.NOCIcon = NOCIcon; exports.ITIcon = ITIcon; exports.OPSIcon = OPSIcon; exports.DashboardIcon = DashboardIcon; exports.DevicesIcon = DevicesIcon; exports.LogsIcon = LogsIcon; exports.OrganizationsIcon = OrganizationsIcon; exports.SettingsIcon = SettingsIcon; exports.LogOutIcon = LogOutIcon; exports.WindowsIcon = WindowsIcon; exports.MacOSIcon = MacOSIcon; exports.LinuxIcon = LinuxIcon; exports.DoubleChevronIcon = DoubleChevronIcon; exports.GridViewIcon = GridViewIcon; exports.TableViewIcon = TableViewIcon; exports.ShieldLockIcon = ShieldLockIcon; exports.ShieldKeyIcon = ShieldKeyIcon; exports.EyeIcon = EyeIcon; exports.ShapeCircleDashIcon = ShapeCircleDashIcon; exports.BuildingsIcon = BuildingsIcon; exports.UsersGroupIcon = UsersGroupIcon; exports.FolderShieldIcon = FolderShieldIcon; exports.ShieldCheckIcon = ShieldCheckIcon; exports.HotelIcon = HotelIcon; exports.JumpInIcon = JumpInIcon; exports.renderSvgIcon = renderSvgIcon; exports.PowerShellIcon = PowerShellIcon; exports.CmdIcon = CmdIcon; exports.BashIcon = BashIcon; exports.PythonIcon = PythonIcon; exports.NushellIcon = NushellIcon; exports.DenoIcon = DenoIcon; exports.DesktopIcon = DesktopIcon; exports.LaptopIcon = LaptopIcon2; exports.ServerIcon = ServerIcon; exports.getDeviceTypeIcon = getDeviceTypeIcon; exports.G2Icon = G2Icon; exports.CapterraIcon = CapterraIcon; exports.TrustpilotIcon = TrustpilotIcon; exports.GetAppIcon = GetAppIcon; exports.CartaIcon = CartaIcon; exports.PilotIcon = PilotIcon;
|
|
4753
|
-
//# sourceMappingURL=chunk-
|
|
4753
|
+
//# sourceMappingURL=chunk-GHHUCCLX.cjs.map
|