@mhosaic/feedback 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-AA24TFKX.mjs → chunk-YNDBZR37.mjs} +264 -65
- package/dist/chunk-YNDBZR37.mjs.map +1 -0
- package/dist/embed.min.js +19 -5
- package/dist/embed.min.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/widget.min.js +20 -6
- package/dist/widget.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AA24TFKX.mjs.map +0 -1
|
@@ -153,10 +153,14 @@ function createApiClient(options) {
|
|
|
153
153
|
if (response.status === 404) return [];
|
|
154
154
|
return readJsonArray(response, "listChangelog");
|
|
155
155
|
}
|
|
156
|
-
async function getReport(reportId, externalId) {
|
|
156
|
+
async function getReport(reportId, externalId, opts) {
|
|
157
157
|
const response = await fetcher(
|
|
158
158
|
`${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
|
|
159
|
-
{
|
|
159
|
+
{
|
|
160
|
+
method: "GET",
|
|
161
|
+
headers: widgetHeaders(externalId),
|
|
162
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
163
|
+
}
|
|
160
164
|
);
|
|
161
165
|
return readJsonObject(response, "getReport");
|
|
162
166
|
}
|
|
@@ -268,10 +272,14 @@ function createApiClient(options) {
|
|
|
268
272
|
const qs = params.toString();
|
|
269
273
|
return qs ? `?${qs}` : "";
|
|
270
274
|
}
|
|
271
|
-
async function listBoard(externalId, filters) {
|
|
275
|
+
async function listBoard(externalId, filters, opts) {
|
|
272
276
|
const response = await fetcher(
|
|
273
277
|
`${endpoint}/api/feedback/v1/reports/widget/board/${boardQueryString(filters)}`,
|
|
274
|
-
{
|
|
278
|
+
{
|
|
279
|
+
method: "GET",
|
|
280
|
+
headers: widgetHeaders(externalId),
|
|
281
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
282
|
+
}
|
|
275
283
|
);
|
|
276
284
|
if (response.status === 404) {
|
|
277
285
|
return { count: 0, next: null, previous: null, results: [] };
|
|
@@ -282,10 +290,14 @@ function createApiClient(options) {
|
|
|
282
290
|
}
|
|
283
291
|
return page;
|
|
284
292
|
}
|
|
285
|
-
async function fetchBoardKpis(externalId, filters) {
|
|
293
|
+
async function fetchBoardKpis(externalId, filters, opts) {
|
|
286
294
|
const response = await fetcher(
|
|
287
295
|
`${endpoint}/api/feedback/v1/reports/widget/board/kpis/${boardQueryString(filters)}`,
|
|
288
|
-
{
|
|
296
|
+
{
|
|
297
|
+
method: "GET",
|
|
298
|
+
headers: widgetHeaders(externalId),
|
|
299
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
300
|
+
}
|
|
289
301
|
);
|
|
290
302
|
if (response.status === 404) {
|
|
291
303
|
return { total: 0, by_status: {}, resolution_rate: 0, scope: "mine" };
|
|
@@ -705,6 +717,7 @@ var DEFAULT_STRINGS = {
|
|
|
705
717
|
"board.retry": "Try again",
|
|
706
718
|
"board.list.you": "you",
|
|
707
719
|
"board.list.count": "{n} of {total}",
|
|
720
|
+
"board.list.loadMore": "Show more",
|
|
708
721
|
"board.detail.empty": "Pick a report on the left to see its full thread.",
|
|
709
722
|
"board.detail.by": "By",
|
|
710
723
|
"board.detail.confirm_resolution": "Confirm resolution",
|
|
@@ -891,6 +904,7 @@ var FRENCH_STRINGS = {
|
|
|
891
904
|
"board.retry": "R\xE9essayer",
|
|
892
905
|
"board.list.you": "vous",
|
|
893
906
|
"board.list.count": "{n} sur {total}",
|
|
907
|
+
"board.list.loadMore": "Afficher plus",
|
|
894
908
|
"board.detail.empty": "S\xE9lectionnez un rapport \xE0 gauche pour voir le fil complet.",
|
|
895
909
|
"board.detail.by": "Par",
|
|
896
910
|
"board.detail.confirm_resolution": "Confirmer la r\xE9solution",
|
|
@@ -1038,6 +1052,26 @@ function onLocationChange(cb) {
|
|
|
1038
1052
|
// src/widget/BoardView.tsx
|
|
1039
1053
|
import { useEffect as useEffect2, useMemo, useState as useState2 } from "preact/hooks";
|
|
1040
1054
|
|
|
1055
|
+
// src/widget/boardCache.ts
|
|
1056
|
+
var TTL_MS = 6e4;
|
|
1057
|
+
var MAX_ENTRIES = 50;
|
|
1058
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1059
|
+
function boardCacheKey(externalId, fetchKey) {
|
|
1060
|
+
return `${externalId}|${fetchKey}`;
|
|
1061
|
+
}
|
|
1062
|
+
function readBoardCache(key) {
|
|
1063
|
+
const hit = cache.get(key);
|
|
1064
|
+
return hit && Date.now() - hit.at < TTL_MS ? hit : null;
|
|
1065
|
+
}
|
|
1066
|
+
function writeBoardCache(key, page, kpis) {
|
|
1067
|
+
if (!cache.has(key) && cache.size >= MAX_ENTRIES) {
|
|
1068
|
+
const oldest = cache.keys().next().value;
|
|
1069
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
1070
|
+
}
|
|
1071
|
+
cache.delete(key);
|
|
1072
|
+
cache.set(key, { page, kpis, at: Date.now() });
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1041
1075
|
// src/widget/boardViewStore.ts
|
|
1042
1076
|
var PREFIX = "mhosaic.boardView.";
|
|
1043
1077
|
function loadBoardView(externalId) {
|
|
@@ -1146,7 +1180,7 @@ function linkifySeq(text, onOpenSeq) {
|
|
|
1146
1180
|
|
|
1147
1181
|
// src/widget/CommentBubble.tsx
|
|
1148
1182
|
import { jsx, jsxs as jsxs2 } from "preact/jsx-runtime";
|
|
1149
|
-
function CommentBubble({ comment, strings, onOpenSeq }) {
|
|
1183
|
+
function CommentBubble({ comment, strings, onOpenSeq, stableUrl }) {
|
|
1150
1184
|
const isMine = comment.is_mine;
|
|
1151
1185
|
const isAgent = !isMine && comment.author_source === "mcp";
|
|
1152
1186
|
const isSystem = !isMine && comment.author_source === "system";
|
|
@@ -1157,17 +1191,20 @@ function CommentBubble({ comment, strings, onOpenSeq }) {
|
|
|
1157
1191
|
return /* @__PURE__ */ jsxs2("div", { class: `comment-bubble ${isMine ? "is-mine" : "is-other"}`, children: [
|
|
1158
1192
|
!isMine && label && /* @__PURE__ */ jsx("div", { class: `comment-author comment-author--${variant}`, children: label }),
|
|
1159
1193
|
comment.body && /* @__PURE__ */ jsx("div", { class: "comment-body", children: linkifySeq(comment.body, onOpenSeq) }),
|
|
1160
|
-
images.length > 0 && /* @__PURE__ */ jsx("div", { class: "comment-attachments", children: images.map((a) =>
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1194
|
+
images.length > 0 && /* @__PURE__ */ jsx("div", { class: "comment-attachments", children: images.map((a) => {
|
|
1195
|
+
const src = stableUrl ? stableUrl(`att:${a.id}`, a.url) : a.url;
|
|
1196
|
+
return /* @__PURE__ */ jsx(
|
|
1197
|
+
"a",
|
|
1198
|
+
{
|
|
1199
|
+
class: "comment-attachment",
|
|
1200
|
+
href: src,
|
|
1201
|
+
target: "_blank",
|
|
1202
|
+
rel: "noopener noreferrer",
|
|
1203
|
+
children: /* @__PURE__ */ jsx("img", { src, alt: strings["detail.attachment_alt"], loading: "lazy" })
|
|
1204
|
+
},
|
|
1205
|
+
a.id
|
|
1206
|
+
);
|
|
1207
|
+
}) }),
|
|
1171
1208
|
/* @__PURE__ */ jsx("div", { class: "comment-time", children: formatTime(comment.created_at) })
|
|
1172
1209
|
] });
|
|
1173
1210
|
}
|
|
@@ -1234,7 +1271,8 @@ function ReportDetailView({
|
|
|
1234
1271
|
canModerate = true,
|
|
1235
1272
|
variant = "modal",
|
|
1236
1273
|
onOpenSeq,
|
|
1237
|
-
buildPermalink
|
|
1274
|
+
buildPermalink,
|
|
1275
|
+
seed
|
|
1238
1276
|
}) {
|
|
1239
1277
|
const [detail, setDetail] = useState(null);
|
|
1240
1278
|
const [error, setError] = useState(null);
|
|
@@ -1251,6 +1289,20 @@ function ReportDetailView({
|
|
|
1251
1289
|
const [attachError, setAttachError] = useState("");
|
|
1252
1290
|
const fileInputRef = useRef(null);
|
|
1253
1291
|
const mountedRef = useRef(true);
|
|
1292
|
+
const urlPins = useRef(/* @__PURE__ */ new Map());
|
|
1293
|
+
const [, bumpPinTick] = useState(0);
|
|
1294
|
+
const PIN_MAX_AGE_MS = 45 * 6e4;
|
|
1295
|
+
const pinUrl = (key, url) => {
|
|
1296
|
+
if (!url) return url;
|
|
1297
|
+
const pin = urlPins.current.get(key);
|
|
1298
|
+
if (pin && Date.now() - pin.at < PIN_MAX_AGE_MS) return pin.url;
|
|
1299
|
+
urlPins.current.set(key, { url, at: Date.now() });
|
|
1300
|
+
return url;
|
|
1301
|
+
};
|
|
1302
|
+
const unpinUrl = (key) => {
|
|
1303
|
+
urlPins.current.delete(key);
|
|
1304
|
+
bumpPinTick((t) => t + 1);
|
|
1305
|
+
};
|
|
1254
1306
|
const shotsRef = useRef(composeShots);
|
|
1255
1307
|
shotsRef.current = composeShots;
|
|
1256
1308
|
useEffect(() => {
|
|
@@ -1421,6 +1473,39 @@ function ReportDetailView({
|
|
|
1421
1473
|
}
|
|
1422
1474
|
};
|
|
1423
1475
|
if (!detail && !error) {
|
|
1476
|
+
if (seed) {
|
|
1477
|
+
const seedPill = /* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${seed.status}`, children: strings[`status.${seed.status}`] ?? seed.status });
|
|
1478
|
+
return /* @__PURE__ */ jsxs3("div", { class: `report-detail variant-${variant} is-seeded`, children: [
|
|
1479
|
+
variant === "modal" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header", children: [
|
|
1480
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
|
|
1481
|
+
"\u2190 ",
|
|
1482
|
+
strings["detail.back"]
|
|
1483
|
+
] }),
|
|
1484
|
+
seedPill
|
|
1485
|
+
] }),
|
|
1486
|
+
variant === "board" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header report-detail-header--board", children: [
|
|
1487
|
+
/* @__PURE__ */ jsxs3(
|
|
1488
|
+
"button",
|
|
1489
|
+
{
|
|
1490
|
+
type: "button",
|
|
1491
|
+
class: "btn btn--ghost report-detail-back",
|
|
1492
|
+
onClick: onBack,
|
|
1493
|
+
"aria-label": strings["board.back"],
|
|
1494
|
+
children: [
|
|
1495
|
+
"\u2190 ",
|
|
1496
|
+
strings["board.back"]
|
|
1497
|
+
]
|
|
1498
|
+
}
|
|
1499
|
+
),
|
|
1500
|
+
seedPill
|
|
1501
|
+
] }),
|
|
1502
|
+
/* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
|
|
1503
|
+
/* @__PURE__ */ jsx2("div", { class: "report-detail-description-row", children: /* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: linkifySeq(seed.description, onOpenSeq) }) }),
|
|
1504
|
+
/* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
|
|
1505
|
+
/* @__PURE__ */ jsx2("div", { class: "mine-loading", children: strings["mine.loading"] })
|
|
1506
|
+
] })
|
|
1507
|
+
] });
|
|
1508
|
+
}
|
|
1424
1509
|
return /* @__PURE__ */ jsx2("div", { class: "mine-loading", children: strings["mine.loading"] });
|
|
1425
1510
|
}
|
|
1426
1511
|
if (!detail) {
|
|
@@ -1530,7 +1615,7 @@ function ReportDetailView({
|
|
|
1530
1615
|
children: copied ? strings["detail.copied"] : strings["detail.copy_link"]
|
|
1531
1616
|
}
|
|
1532
1617
|
) }),
|
|
1533
|
-
(detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).
|
|
1618
|
+
(detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).map((url, i) => ({ url: pinUrl(`shot:${i}`, url ?? null), pinKey: `shot:${i}` })).filter((s) => Boolean(s.url)).map(({ url, pinKey }) => {
|
|
1534
1619
|
const safeHref = safeExternalHref(url);
|
|
1535
1620
|
return safeHref ? /* @__PURE__ */ jsx2(
|
|
1536
1621
|
"a",
|
|
@@ -1539,9 +1624,9 @@ function ReportDetailView({
|
|
|
1539
1624
|
href: safeHref,
|
|
1540
1625
|
target: "_blank",
|
|
1541
1626
|
rel: "noopener noreferrer",
|
|
1542
|
-
children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" })
|
|
1627
|
+
children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy", onError: () => unpinUrl(pinKey) })
|
|
1543
1628
|
}
|
|
1544
|
-
) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" }) });
|
|
1629
|
+
) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy", onError: () => unpinUrl(pinKey) }) });
|
|
1545
1630
|
}),
|
|
1546
1631
|
/* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
|
|
1547
1632
|
detail.comments.length === 0 ? /* @__PURE__ */ jsx2("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx2("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(
|
|
@@ -1549,7 +1634,8 @@ function ReportDetailView({
|
|
|
1549
1634
|
{
|
|
1550
1635
|
comment: c,
|
|
1551
1636
|
strings,
|
|
1552
|
-
...onOpenSeq && { onOpenSeq }
|
|
1637
|
+
...onOpenSeq && { onOpenSeq },
|
|
1638
|
+
stableUrl: (key, url) => pinUrl(key, url) ?? url
|
|
1553
1639
|
}
|
|
1554
1640
|
) })) }),
|
|
1555
1641
|
detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx2(StatusHistorySection, { rows: detail.status_history, strings }),
|
|
@@ -1833,10 +1919,13 @@ function BoardView({
|
|
|
1833
1919
|
const [page, setPage] = useState2(null);
|
|
1834
1920
|
const [kpis, setKpis] = useState2(null);
|
|
1835
1921
|
const [loading, setLoading] = useState2(true);
|
|
1922
|
+
const [stale, setStale] = useState2(false);
|
|
1836
1923
|
const [error, setError] = useState2(null);
|
|
1837
1924
|
const [selectedId, setSelectedId] = useState2(initialSelectedId ?? null);
|
|
1838
1925
|
const [detailKey, setDetailKey] = useState2(0);
|
|
1839
1926
|
const [reloadTick, setReloadTick] = useState2(0);
|
|
1927
|
+
const [extra, setExtra] = useState2(null);
|
|
1928
|
+
const [loadingMore, setLoadingMore] = useState2(false);
|
|
1840
1929
|
const [qDraft, setQDraft] = useState2(filters.q ?? "");
|
|
1841
1930
|
const debouncedQ = useDebouncedValue(qDraft, SEARCH_DEBOUNCE_MS);
|
|
1842
1931
|
useEffect2(() => {
|
|
@@ -1866,18 +1955,36 @@ function BoardView({
|
|
|
1866
1955
|
}, [filtersHash(filters), thisPage, reloadTick]);
|
|
1867
1956
|
useEffect2(() => {
|
|
1868
1957
|
let cancelled = false;
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1958
|
+
const controller = new AbortController();
|
|
1959
|
+
const cacheKey = boardCacheKey(externalId, JSON.stringify(fetchFilters));
|
|
1960
|
+
const cached = readBoardCache(cacheKey);
|
|
1961
|
+
if (cached) {
|
|
1962
|
+
setPage(cached.page);
|
|
1963
|
+
setKpis(cached.kpis);
|
|
1964
|
+
setStale(false);
|
|
1965
|
+
setLoading(false);
|
|
1966
|
+
} else {
|
|
1967
|
+
setPage(
|
|
1968
|
+
(prev) => prev ? { ...prev, results: applyLocalFilters(prev.results, fetchFilters) } : null
|
|
1969
|
+
);
|
|
1970
|
+
setStale(true);
|
|
1971
|
+
setLoading(true);
|
|
1972
|
+
}
|
|
1973
|
+
setExtra(null);
|
|
1974
|
+
setLoadingMore(false);
|
|
1872
1975
|
const poll = startPoll(async () => {
|
|
1873
1976
|
try {
|
|
1874
|
-
const
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1977
|
+
const list = await api.listBoard(externalId, fetchFilters, {
|
|
1978
|
+
signal: controller.signal
|
|
1979
|
+
});
|
|
1980
|
+
const k = list.kpis ?? await api.fetchBoardKpis(externalId, fetchFilters, {
|
|
1981
|
+
signal: controller.signal
|
|
1982
|
+
});
|
|
1878
1983
|
if (cancelled) return true;
|
|
1984
|
+
writeBoardCache(cacheKey, list, k);
|
|
1879
1985
|
setPage(list);
|
|
1880
1986
|
setKpis(k);
|
|
1987
|
+
setStale(false);
|
|
1881
1988
|
setError(null);
|
|
1882
1989
|
return true;
|
|
1883
1990
|
} catch (e) {
|
|
@@ -1891,17 +1998,41 @@ function BoardView({
|
|
|
1891
1998
|
return () => {
|
|
1892
1999
|
cancelled = true;
|
|
1893
2000
|
poll.stop();
|
|
2001
|
+
controller.abort();
|
|
1894
2002
|
};
|
|
1895
2003
|
}, [api, externalId, JSON.stringify(fetchFilters)]);
|
|
2004
|
+
const visibleRows = useMemo(() => {
|
|
2005
|
+
if (!page) return [];
|
|
2006
|
+
if (!extra || extra.rows.length === 0) return page.results;
|
|
2007
|
+
const seen = new Set(page.results.map((r) => r.id));
|
|
2008
|
+
return [...page.results, ...extra.rows.filter((r) => !seen.has(r.id))];
|
|
2009
|
+
}, [page, extra]);
|
|
2010
|
+
const hasMore = extra ? extra.hasMore : Boolean(page?.next);
|
|
2011
|
+
const loadMore = async () => {
|
|
2012
|
+
if (loadingMore || !page) return;
|
|
2013
|
+
const pageNum = extra ? extra.nextPage : 2;
|
|
2014
|
+
setLoadingMore(true);
|
|
2015
|
+
try {
|
|
2016
|
+
const res = await api.listBoard(externalId, { ...fetchFilters, page: pageNum });
|
|
2017
|
+
setExtra((prev) => ({
|
|
2018
|
+
rows: [...prev?.rows ?? [], ...res.results],
|
|
2019
|
+
nextPage: pageNum + 1,
|
|
2020
|
+
hasMore: res.next !== null
|
|
2021
|
+
}));
|
|
2022
|
+
} catch {
|
|
2023
|
+
} finally {
|
|
2024
|
+
setLoadingMore(false);
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
1896
2027
|
const selectedRow = useMemo(() => {
|
|
1897
|
-
if (!selectedId
|
|
1898
|
-
return
|
|
1899
|
-
}, [selectedId,
|
|
2028
|
+
if (!selectedId) return null;
|
|
2029
|
+
return visibleRows.find((r) => r.id === selectedId) ?? null;
|
|
2030
|
+
}, [selectedId, visibleRows]);
|
|
1900
2031
|
const onPickRow = (row) => {
|
|
1901
2032
|
setSelectedId(row.id);
|
|
1902
2033
|
setDetailKey((k) => k + 1);
|
|
1903
2034
|
};
|
|
1904
|
-
return /* @__PURE__ */ jsxs4("div", { class:
|
|
2035
|
+
return /* @__PURE__ */ jsxs4("div", { class: `board-view ${stale ? "is-stale" : ""}`, children: [
|
|
1905
2036
|
/* @__PURE__ */ jsx3(BoardHeader, { strings, kpis, thisPage }),
|
|
1906
2037
|
/* @__PURE__ */ jsx3(
|
|
1907
2038
|
BoardFilters,
|
|
@@ -1917,7 +2048,7 @@ function BoardView({
|
|
|
1917
2048
|
}
|
|
1918
2049
|
),
|
|
1919
2050
|
/* @__PURE__ */ jsxs4("div", { class: "board-body", children: [
|
|
1920
|
-
/* @__PURE__ */ jsxs4("div", { class: "board-list-wrap", "aria-busy": loading
|
|
2051
|
+
/* @__PURE__ */ jsxs4("div", { class: "board-list-wrap", "aria-busy": loading, children: [
|
|
1921
2052
|
error && /* @__PURE__ */ jsxs4("div", { class: "board-error", role: "alert", children: [
|
|
1922
2053
|
/* @__PURE__ */ jsx3("span", { children: error }),
|
|
1923
2054
|
/* @__PURE__ */ jsx3(
|
|
@@ -1935,7 +2066,7 @@ function BoardView({
|
|
|
1935
2066
|
)
|
|
1936
2067
|
] }),
|
|
1937
2068
|
!error && loading && !page && /* @__PURE__ */ jsx3(BoardListSkeleton, {}),
|
|
1938
|
-
!error && page &&
|
|
2069
|
+
!error && page && visibleRows.length === 0 && (!loading || stale) && /* @__PURE__ */ jsx3(
|
|
1939
2070
|
BoardEmpty,
|
|
1940
2071
|
{
|
|
1941
2072
|
strings,
|
|
@@ -1943,27 +2074,40 @@ function BoardView({
|
|
|
1943
2074
|
onAllPages: () => setThisPage(false)
|
|
1944
2075
|
}
|
|
1945
2076
|
),
|
|
1946
|
-
!error && page &&
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2077
|
+
!error && page && visibleRows.length > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2078
|
+
/* @__PURE__ */ jsx3(
|
|
2079
|
+
BoardList,
|
|
2080
|
+
{
|
|
2081
|
+
rows: visibleRows,
|
|
2082
|
+
selectedId,
|
|
2083
|
+
onPick: onPickRow,
|
|
2084
|
+
strings,
|
|
2085
|
+
total: page.count,
|
|
2086
|
+
thisPage
|
|
2087
|
+
}
|
|
2088
|
+
),
|
|
2089
|
+
hasMore && !stale && /* @__PURE__ */ jsx3(
|
|
2090
|
+
"button",
|
|
2091
|
+
{
|
|
2092
|
+
type: "button",
|
|
2093
|
+
class: "btn btn--ghost board-load-more",
|
|
2094
|
+
onClick: () => void loadMore(),
|
|
2095
|
+
disabled: loadingMore,
|
|
2096
|
+
children: strings["board.list.loadMore"]
|
|
2097
|
+
}
|
|
2098
|
+
)
|
|
2099
|
+
] })
|
|
1957
2100
|
] }),
|
|
1958
|
-
/* @__PURE__ */ jsx3("div", { class: `board-detail-wrap ${
|
|
2101
|
+
/* @__PURE__ */ jsx3("div", { class: `board-detail-wrap ${selectedId ? "has-selection" : ""}`, children: selectedId ? /* @__PURE__ */ jsx3(
|
|
1959
2102
|
ReportDetailView,
|
|
1960
2103
|
{
|
|
1961
2104
|
api,
|
|
1962
2105
|
externalId,
|
|
1963
|
-
reportId:
|
|
2106
|
+
reportId: selectedId,
|
|
1964
2107
|
strings,
|
|
1965
2108
|
onBack: () => setSelectedId(null),
|
|
1966
|
-
canModerate: selectedRow.can_moderate ?? selectedRow.is_mine,
|
|
2109
|
+
canModerate: selectedRow ? selectedRow.can_moderate ?? selectedRow.is_mine : false,
|
|
2110
|
+
...selectedRow ? { seed: selectedRow } : {},
|
|
1967
2111
|
variant: "board",
|
|
1968
2112
|
onOpenSeq: (seq) => {
|
|
1969
2113
|
void api.getReportBySeq(seq, externalId).then((r) => {
|
|
@@ -2285,6 +2429,33 @@ function useDebouncedValue(value, ms) {
|
|
|
2285
2429
|
}, [value, ms]);
|
|
2286
2430
|
return debounced;
|
|
2287
2431
|
}
|
|
2432
|
+
var SEVERITY_RANK = { blocker: 0, high: 1, medium: 2, low: 3 };
|
|
2433
|
+
function applyLocalFilters(rows, f) {
|
|
2434
|
+
let out = rows;
|
|
2435
|
+
if (f.status?.length) out = out.filter((r) => f.status.includes(r.status));
|
|
2436
|
+
if (f.type?.length) out = out.filter((r) => f.type.includes(r.feedback_type));
|
|
2437
|
+
if (f.severity?.length) out = out.filter((r) => f.severity.includes(r.severity));
|
|
2438
|
+
if (f.mine) out = out.filter((r) => r.is_mine);
|
|
2439
|
+
const sorted = out.slice();
|
|
2440
|
+
switch (f.ordering ?? "recent") {
|
|
2441
|
+
case "oldest":
|
|
2442
|
+
sorted.sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
2443
|
+
break;
|
|
2444
|
+
case "updated":
|
|
2445
|
+
sorted.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
2446
|
+
break;
|
|
2447
|
+
case "severity":
|
|
2448
|
+
sorted.sort(
|
|
2449
|
+
(a, b) => (SEVERITY_RANK[a.severity] ?? 9) - (SEVERITY_RANK[b.severity] ?? 9)
|
|
2450
|
+
);
|
|
2451
|
+
break;
|
|
2452
|
+
case "status":
|
|
2453
|
+
break;
|
|
2454
|
+
default:
|
|
2455
|
+
sorted.sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
2456
|
+
}
|
|
2457
|
+
return sorted;
|
|
2458
|
+
}
|
|
2288
2459
|
function filtersHash(f) {
|
|
2289
2460
|
return JSON.stringify({
|
|
2290
2461
|
s: f.status?.slice().sort(),
|
|
@@ -2340,7 +2511,7 @@ function repliesLabel(count, strings) {
|
|
|
2340
2511
|
if (count === 1) return strings["mine.replies_one"];
|
|
2341
2512
|
return strings["mine.replies_many"].replace("{count}", String(count));
|
|
2342
2513
|
}
|
|
2343
|
-
function ReportRow({ row, strings, onClick }) {
|
|
2514
|
+
function ReportRow({ row, strings, onClick, extraMeta }) {
|
|
2344
2515
|
const preview = row.description.length > 120 ? row.description.slice(0, 117) + "\u2026" : row.description;
|
|
2345
2516
|
return /* @__PURE__ */ jsxs5("button", { type: "button", class: "mine-row", onClick, children: [
|
|
2346
2517
|
/* @__PURE__ */ jsxs5("div", { class: "mine-row-pills", children: [
|
|
@@ -2354,6 +2525,10 @@ function ReportRow({ row, strings, onClick }) {
|
|
|
2354
2525
|
row.comment_count > 0 && /* @__PURE__ */ jsxs5("span", { children: [
|
|
2355
2526
|
"\xB7 ",
|
|
2356
2527
|
repliesLabel(row.comment_count, strings)
|
|
2528
|
+
] }),
|
|
2529
|
+
extraMeta && /* @__PURE__ */ jsxs5("span", { children: [
|
|
2530
|
+
"\xB7 ",
|
|
2531
|
+
extraMeta
|
|
2357
2532
|
] })
|
|
2358
2533
|
] })
|
|
2359
2534
|
] });
|
|
@@ -2458,7 +2633,15 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
|
|
|
2458
2633
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-rule", "aria-hidden": "true" }),
|
|
2459
2634
|
/* @__PURE__ */ jsx5("span", { class: "changelog-group-count", children: strings[g.rows.length === 1 ? "changelog.resolved_one" : "changelog.resolved_many"].replace("{count}", String(g.rows.length)) })
|
|
2460
2635
|
] }),
|
|
2461
|
-
/* @__PURE__ */ jsx5("ul", { class: "mine-rows", children: g.rows.map((row) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
|
|
2636
|
+
/* @__PURE__ */ jsx5("ul", { class: "mine-rows", children: g.rows.map((row) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(
|
|
2637
|
+
ReportRow,
|
|
2638
|
+
{
|
|
2639
|
+
row,
|
|
2640
|
+
strings,
|
|
2641
|
+
onClick: () => onSelect(row),
|
|
2642
|
+
extraMeta: row.pr_number ? `PR #${row.pr_number}` : void 0
|
|
2643
|
+
}
|
|
2644
|
+
) })) })
|
|
2462
2645
|
] }, g.weekKey)) })
|
|
2463
2646
|
] });
|
|
2464
2647
|
}
|
|
@@ -3635,30 +3818,32 @@ function PagePeekPanel({
|
|
|
3635
3818
|
|
|
3636
3819
|
// src/widget/pageSignal.ts
|
|
3637
3820
|
import { useCallback, useEffect as useEffect9, useState as useState8 } from "preact/hooks";
|
|
3638
|
-
var
|
|
3821
|
+
var TTL_MS2 = 6e4;
|
|
3639
3822
|
function computeOpenCount(kpis) {
|
|
3640
3823
|
const s = kpis.by_status;
|
|
3641
3824
|
return (s.new ?? 0) + (s.in_progress ?? 0) + (s.awaiting_validation ?? 0);
|
|
3642
3825
|
}
|
|
3643
|
-
var
|
|
3826
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
3644
3827
|
var inflight = /* @__PURE__ */ new Map();
|
|
3645
3828
|
function invalidatePageSignal() {
|
|
3646
|
-
|
|
3829
|
+
cache2.clear();
|
|
3647
3830
|
}
|
|
3648
|
-
function
|
|
3831
|
+
function fetchPageKpis(api, externalId, path) {
|
|
3649
3832
|
const key = `${externalId}|${path}`;
|
|
3650
|
-
const hit =
|
|
3651
|
-
if (hit && Date.now() - hit.fetchedAt <
|
|
3833
|
+
const hit = cache2.get(key);
|
|
3834
|
+
if (hit && Date.now() - hit.fetchedAt < TTL_MS2) return Promise.resolve(hit.kpis);
|
|
3652
3835
|
const pending = inflight.get(key);
|
|
3653
3836
|
if (pending) return pending;
|
|
3654
3837
|
const p = api.fetchBoardKpis(externalId, { pagePath: path }).then((kpis) => {
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
return open;
|
|
3838
|
+
cache2.set(key, { kpis, fetchedAt: Date.now() });
|
|
3839
|
+
return kpis;
|
|
3658
3840
|
}).finally(() => inflight.delete(key));
|
|
3659
3841
|
inflight.set(key, p);
|
|
3660
3842
|
return p;
|
|
3661
3843
|
}
|
|
3844
|
+
function fetchOpen(api, externalId, path) {
|
|
3845
|
+
return fetchPageKpis(api, externalId, path).then(computeOpenCount);
|
|
3846
|
+
}
|
|
3662
3847
|
function usePageOpenCount(opts) {
|
|
3663
3848
|
const { api, externalId, getCurrentPage, enabled } = opts;
|
|
3664
3849
|
const [open, setOpen] = useState8(0);
|
|
@@ -5452,6 +5637,20 @@ button.report-detail-edit {
|
|
|
5452
5637
|
min-height: 0;
|
|
5453
5638
|
overflow: hidden;
|
|
5454
5639
|
}
|
|
5640
|
+
/* Stale-while-revalidate: a query change keeps the previous rows visible
|
|
5641
|
+
* (locally pre-filtered where the row data allows) and dims them while the
|
|
5642
|
+
* server confirms \u2014 the instant local response is the #352 "never inert"
|
|
5643
|
+
* guarantee, the dim is the "still confirming" affordance. */
|
|
5644
|
+
.board-view.is-stale .board-list,
|
|
5645
|
+
.board-view.is-stale .board-list-count,
|
|
5646
|
+
.board-view.is-stale .board-kpi-strip {
|
|
5647
|
+
opacity: 0.55;
|
|
5648
|
+
transition: opacity 0.15s ease;
|
|
5649
|
+
}
|
|
5650
|
+
.board-load-more {
|
|
5651
|
+
margin: 4px auto 8px;
|
|
5652
|
+
flex: 0 0 auto;
|
|
5653
|
+
}
|
|
5455
5654
|
.board-list-count {
|
|
5456
5655
|
font-size: var(--mfb-text-xs);
|
|
5457
5656
|
color: var(--mfb-text-muted);
|
|
@@ -5773,7 +5972,7 @@ function mountWidget(options) {
|
|
|
5773
5972
|
function openWidget(externalId) {
|
|
5774
5973
|
rerender({ ...currentState, open: true, tab: "send" });
|
|
5775
5974
|
if (options.openToCurrentPageFeedback && options.api && externalId) {
|
|
5776
|
-
options.api
|
|
5975
|
+
fetchPageKpis(options.api, externalId, currentPagePath(options)).then((kpis) => {
|
|
5777
5976
|
if (kpis.total > 0 && currentState.open && currentState.tab === "send") {
|
|
5778
5977
|
rerender({ ...currentState, tab: "board" });
|
|
5779
5978
|
}
|
|
@@ -6205,8 +6404,8 @@ function createFeedback(config) {
|
|
|
6205
6404
|
capture_method: captureMethod,
|
|
6206
6405
|
technical_context
|
|
6207
6406
|
};
|
|
6208
|
-
if ("0.
|
|
6209
|
-
payload.widget_version = "0.
|
|
6407
|
+
if ("0.32.0") {
|
|
6408
|
+
payload.widget_version = "0.32.0";
|
|
6210
6409
|
}
|
|
6211
6410
|
if (manualScreenshots?.length) {
|
|
6212
6411
|
payload.screenshots = manualScreenshots;
|
|
@@ -6332,4 +6531,4 @@ function createFeedback(config) {
|
|
|
6332
6531
|
export {
|
|
6333
6532
|
createFeedback
|
|
6334
6533
|
};
|
|
6335
|
-
//# sourceMappingURL=chunk-
|
|
6534
|
+
//# sourceMappingURL=chunk-YNDBZR37.mjs.map
|