@mhosaic/feedback 0.31.0 → 0.33.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.
@@ -145,6 +145,32 @@ function createApiClient(options) {
145
145
  if (response.status === 404) return [];
146
146
  return readJsonArray(response, "listMine");
147
147
  }
148
+ async function diagnose(opts) {
149
+ const onLine = opts?.onLine ?? (typeof navigator !== "undefined" ? navigator.onLine : true);
150
+ if (onLine === false) return "offline";
151
+ const probeUrl = `${endpoint}/api/feedback/v1/widget-manifest/?pk=${encodeURIComponent(options.apiKey)}`;
152
+ let simple;
153
+ try {
154
+ simple = await fetcher(probeUrl, { method: "GET", redirect: "manual" });
155
+ } catch {
156
+ return "blocked";
157
+ }
158
+ if (simple.type === "opaqueredirect" || simple.status >= 300 && simple.status < 400) {
159
+ return "intercepted";
160
+ }
161
+ let authed;
162
+ try {
163
+ authed = await fetcher(probeUrl, {
164
+ method: "GET",
165
+ headers: { Authorization: `Bearer ${options.apiKey}` }
166
+ });
167
+ } catch {
168
+ return "preflight";
169
+ }
170
+ if (authed.status === 401 || authed.status === 403) return "key";
171
+ if (authed.status === 429) return "throttle";
172
+ return "healthy";
173
+ }
148
174
  async function listChangelog(externalId) {
149
175
  const response = await fetcher(
150
176
  `${endpoint}/api/feedback/v1/reports/widget/changelog/`,
@@ -153,10 +179,14 @@ function createApiClient(options) {
153
179
  if (response.status === 404) return [];
154
180
  return readJsonArray(response, "listChangelog");
155
181
  }
156
- async function getReport(reportId, externalId) {
182
+ async function getReport(reportId, externalId, opts) {
157
183
  const response = await fetcher(
158
184
  `${endpoint}/api/feedback/v1/reports/widget/${reportId}/`,
159
- { method: "GET", headers: widgetHeaders(externalId) }
185
+ {
186
+ method: "GET",
187
+ headers: widgetHeaders(externalId),
188
+ ...opts?.signal ? { signal: opts.signal } : {}
189
+ }
160
190
  );
161
191
  return readJsonObject(response, "getReport");
162
192
  }
@@ -268,10 +298,14 @@ function createApiClient(options) {
268
298
  const qs = params.toString();
269
299
  return qs ? `?${qs}` : "";
270
300
  }
271
- async function listBoard(externalId, filters) {
301
+ async function listBoard(externalId, filters, opts) {
272
302
  const response = await fetcher(
273
303
  `${endpoint}/api/feedback/v1/reports/widget/board/${boardQueryString(filters)}`,
274
- { method: "GET", headers: widgetHeaders(externalId) }
304
+ {
305
+ method: "GET",
306
+ headers: widgetHeaders(externalId),
307
+ ...opts?.signal ? { signal: opts.signal } : {}
308
+ }
275
309
  );
276
310
  if (response.status === 404) {
277
311
  return { count: 0, next: null, previous: null, results: [] };
@@ -282,10 +316,14 @@ function createApiClient(options) {
282
316
  }
283
317
  return page;
284
318
  }
285
- async function fetchBoardKpis(externalId, filters) {
319
+ async function fetchBoardKpis(externalId, filters, opts) {
286
320
  const response = await fetcher(
287
321
  `${endpoint}/api/feedback/v1/reports/widget/board/kpis/${boardQueryString(filters)}`,
288
- { method: "GET", headers: widgetHeaders(externalId) }
322
+ {
323
+ method: "GET",
324
+ headers: widgetHeaders(externalId),
325
+ ...opts?.signal ? { signal: opts.signal } : {}
326
+ }
289
327
  );
290
328
  if (response.status === 404) {
291
329
  return { total: 0, by_status: {}, resolution_rate: 0, scope: "mine" };
@@ -318,6 +356,14 @@ function createApiClient(options) {
318
356
  checkVisibility,
319
357
  listMine,
320
358
  listChangelog,
359
+ diagnose,
360
+ endpointHost: (() => {
361
+ try {
362
+ return new URL(endpoint).host;
363
+ } catch {
364
+ return endpoint;
365
+ }
366
+ })(),
321
367
  getReport,
322
368
  getReportBySeq,
323
369
  addComment,
@@ -705,6 +751,7 @@ var DEFAULT_STRINGS = {
705
751
  "board.retry": "Try again",
706
752
  "board.list.you": "you",
707
753
  "board.list.count": "{n} of {total}",
754
+ "board.list.loadMore": "Show more",
708
755
  "board.detail.empty": "Pick a report on the left to see its full thread.",
709
756
  "board.detail.by": "By",
710
757
  "board.detail.confirm_resolution": "Confirm resolution",
@@ -726,6 +773,15 @@ var DEFAULT_STRINGS = {
726
773
  "mine.empty.title": "No reports yet",
727
774
  "mine.empty.body": "Once you send feedback you can follow the thread here.",
728
775
  "mine.refresh": "Refresh",
776
+ "diag.run": "Diagnose",
777
+ "diag.running": "Checking the connection\u2026",
778
+ "diag.offline": "You appear to be offline. Reconnect and retry.",
779
+ "diag.blocked": "The feedback service is unreachable from this network. A firewall or proxy is blocking it \u2014 ask IT to allow {host}.",
780
+ "diag.intercepted": "Your corporate network (SSL-inspection proxy) is intercepting widget traffic. Ask IT to add {host} to the inspection bypass list.",
781
+ "diag.preflight": "A network proxy is blocking the widget\u2019s secure requests (CORS preflight). Ask IT to allow {host}.",
782
+ "diag.key": "The widget\u2019s access key was rejected. Contact the site team.",
783
+ "diag.throttle": "Too many requests right now \u2014 wait a moment and retry.",
784
+ "diag.healthy": "The service is reachable \u2014 this looks like a temporary hiccup. Retry.",
729
785
  "mine.loading": "Loading\u2026",
730
786
  "mine.error": "Could not load your reports.",
731
787
  "mine.jump.placeholder": "#",
@@ -891,6 +947,7 @@ var FRENCH_STRINGS = {
891
947
  "board.retry": "R\xE9essayer",
892
948
  "board.list.you": "vous",
893
949
  "board.list.count": "{n} sur {total}",
950
+ "board.list.loadMore": "Afficher plus",
894
951
  "board.detail.empty": "S\xE9lectionnez un rapport \xE0 gauche pour voir le fil complet.",
895
952
  "board.detail.by": "Par",
896
953
  "board.detail.confirm_resolution": "Confirmer la r\xE9solution",
@@ -912,6 +969,15 @@ var FRENCH_STRINGS = {
912
969
  "mine.empty.title": "Aucun rapport",
913
970
  "mine.empty.body": "Apr\xE8s votre premier envoi vous pourrez suivre la conversation ici.",
914
971
  "mine.refresh": "Actualiser",
972
+ "diag.run": "Diagnostiquer",
973
+ "diag.running": "V\xE9rification de la connexion\u2026",
974
+ "diag.offline": "Vous semblez hors ligne. Reconnectez-vous puis r\xE9essayez.",
975
+ "diag.blocked": "Le service de feedback est injoignable depuis ce r\xE9seau. Un pare-feu ou proxy le bloque \u2014 demandez \xE0 votre TI d\u2019autoriser {host}.",
976
+ "diag.intercepted": "Votre r\xE9seau d\u2019entreprise (inspection SSL) intercepte le trafic du widget. Demandez \xE0 votre TI d\u2019ajouter {host} \xE0 la liste d\u2019exclusion.",
977
+ "diag.preflight": "Un proxy r\xE9seau bloque les requ\xEAtes s\xE9curis\xE9es du widget (pr\xE9flight CORS). Demandez \xE0 votre TI d\u2019autoriser {host}.",
978
+ "diag.key": "La cl\xE9 d\u2019acc\xE8s du widget a \xE9t\xE9 refus\xE9e. Contactez l\u2019\xE9quipe du site.",
979
+ "diag.throttle": "Trop de requ\xEAtes pour le moment \u2014 patientez puis r\xE9essayez.",
980
+ "diag.healthy": "Le service est joignable \u2014 le probl\xE8me semble passager. R\xE9essayez.",
915
981
  "mine.loading": "Chargement\u2026",
916
982
  "mine.error": "Impossible de charger vos rapports.",
917
983
  "mine.jump.placeholder": "#",
@@ -1003,7 +1069,7 @@ function resolveStrings(overrides, options = {}) {
1003
1069
 
1004
1070
  // src/widget/mount.tsx
1005
1071
  import { h, render } from "preact";
1006
- import { useCallback as useCallback2, useEffect as useEffect10, useRef as useRef7, useState as useState9 } from "preact/hooks";
1072
+ import { useCallback as useCallback2, useEffect as useEffect10, useRef as useRef7, useState as useState10 } from "preact/hooks";
1007
1073
 
1008
1074
  // src/widget/currentPage.ts
1009
1075
  function currentPagePath(opts) {
@@ -1036,7 +1102,50 @@ function onLocationChange(cb) {
1036
1102
  }
1037
1103
 
1038
1104
  // src/widget/BoardView.tsx
1039
- import { useEffect as useEffect2, useMemo, useState as useState2 } from "preact/hooks";
1105
+ import { useEffect as useEffect2, useMemo, useState as useState3 } from "preact/hooks";
1106
+
1107
+ // src/widget/DiagnosticHint.tsx
1108
+ import { useState } from "preact/hooks";
1109
+ import { jsx } from "preact/jsx-runtime";
1110
+ function DiagnosticHint({ api, strings, host }) {
1111
+ const [state, setState] = useState("idle");
1112
+ const run = async () => {
1113
+ setState("running");
1114
+ try {
1115
+ setState(await api.diagnose());
1116
+ } catch {
1117
+ setState("blocked");
1118
+ }
1119
+ };
1120
+ if (state === "idle") {
1121
+ return /* @__PURE__ */ jsx("button", { type: "button", class: "btn diag-btn", onClick: () => void run(), children: strings["diag.run"] });
1122
+ }
1123
+ if (state === "running") {
1124
+ return /* @__PURE__ */ jsx("div", { class: "diag-result", children: strings["diag.running"] });
1125
+ }
1126
+ const key = `diag.${state}`;
1127
+ return /* @__PURE__ */ jsx("div", { class: "diag-result", children: (strings[key] ?? state).replace("{host}", host) });
1128
+ }
1129
+
1130
+ // src/widget/boardCache.ts
1131
+ var TTL_MS = 6e4;
1132
+ var MAX_ENTRIES = 50;
1133
+ var cache = /* @__PURE__ */ new Map();
1134
+ function boardCacheKey(externalId, fetchKey) {
1135
+ return `${externalId}|${fetchKey}`;
1136
+ }
1137
+ function readBoardCache(key) {
1138
+ const hit = cache.get(key);
1139
+ return hit && Date.now() - hit.at < TTL_MS ? hit : null;
1140
+ }
1141
+ function writeBoardCache(key, page, kpis) {
1142
+ if (!cache.has(key) && cache.size >= MAX_ENTRIES) {
1143
+ const oldest = cache.keys().next().value;
1144
+ if (oldest !== void 0) cache.delete(oldest);
1145
+ }
1146
+ cache.delete(key);
1147
+ cache.set(key, { page, kpis, at: Date.now() });
1148
+ }
1040
1149
 
1041
1150
  // src/widget/boardViewStore.ts
1042
1151
  var PREFIX = "mhosaic.boardView.";
@@ -1108,7 +1217,7 @@ function rateLimitMessage(err, strings) {
1108
1217
  }
1109
1218
 
1110
1219
  // src/widget/ReportDetailView.tsx
1111
- import { useEffect, useRef, useState } from "preact/hooks";
1220
+ import { useEffect, useRef, useState as useState2 } from "preact/hooks";
1112
1221
 
1113
1222
  // src/widget/linkifySeq.tsx
1114
1223
  import { jsxs } from "preact/jsx-runtime";
@@ -1145,8 +1254,8 @@ function linkifySeq(text, onOpenSeq) {
1145
1254
  }
1146
1255
 
1147
1256
  // src/widget/CommentBubble.tsx
1148
- import { jsx, jsxs as jsxs2 } from "preact/jsx-runtime";
1149
- function CommentBubble({ comment, strings, onOpenSeq }) {
1257
+ import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
1258
+ function CommentBubble({ comment, strings, onOpenSeq, stableUrl }) {
1150
1259
  const isMine = comment.is_mine;
1151
1260
  const isAgent = !isMine && comment.author_source === "mcp";
1152
1261
  const isSystem = !isMine && comment.author_source === "system";
@@ -1155,20 +1264,23 @@ function CommentBubble({ comment, strings, onOpenSeq }) {
1155
1264
  const label = comment.author_label || strings[labelKey];
1156
1265
  const images = (comment.attachments ?? []).filter((a) => a.url);
1157
1266
  return /* @__PURE__ */ jsxs2("div", { class: `comment-bubble ${isMine ? "is-mine" : "is-other"}`, children: [
1158
- !isMine && label && /* @__PURE__ */ jsx("div", { class: `comment-author comment-author--${variant}`, children: label }),
1159
- 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) => /* @__PURE__ */ jsx(
1161
- "a",
1162
- {
1163
- class: "comment-attachment",
1164
- href: a.url,
1165
- target: "_blank",
1166
- rel: "noopener noreferrer",
1167
- children: /* @__PURE__ */ jsx("img", { src: a.url, alt: strings["detail.attachment_alt"], loading: "lazy" })
1168
- },
1169
- a.id
1170
- )) }),
1171
- /* @__PURE__ */ jsx("div", { class: "comment-time", children: formatTime(comment.created_at) })
1267
+ !isMine && label && /* @__PURE__ */ jsx2("div", { class: `comment-author comment-author--${variant}`, children: label }),
1268
+ comment.body && /* @__PURE__ */ jsx2("div", { class: "comment-body", children: linkifySeq(comment.body, onOpenSeq) }),
1269
+ images.length > 0 && /* @__PURE__ */ jsx2("div", { class: "comment-attachments", children: images.map((a) => {
1270
+ const src = stableUrl ? stableUrl(`att:${a.id}`, a.url) : a.url;
1271
+ return /* @__PURE__ */ jsx2(
1272
+ "a",
1273
+ {
1274
+ class: "comment-attachment",
1275
+ href: src,
1276
+ target: "_blank",
1277
+ rel: "noopener noreferrer",
1278
+ children: /* @__PURE__ */ jsx2("img", { src, alt: strings["detail.attachment_alt"], loading: "lazy" })
1279
+ },
1280
+ a.id
1281
+ );
1282
+ }) }),
1283
+ /* @__PURE__ */ jsx2("div", { class: "comment-time", children: formatTime(comment.created_at) })
1172
1284
  ] });
1173
1285
  }
1174
1286
  function formatTime(iso) {
@@ -1223,7 +1335,7 @@ function safeExternalHref(url) {
1223
1335
  }
1224
1336
 
1225
1337
  // src/widget/ReportDetailView.tsx
1226
- import { Fragment, jsx as jsx2, jsxs as jsxs3 } from "preact/jsx-runtime";
1338
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
1227
1339
  var POLL_MS = 3e4;
1228
1340
  function ReportDetailView({
1229
1341
  api,
@@ -1234,23 +1346,38 @@ function ReportDetailView({
1234
1346
  canModerate = true,
1235
1347
  variant = "modal",
1236
1348
  onOpenSeq,
1237
- buildPermalink
1349
+ buildPermalink,
1350
+ seed
1238
1351
  }) {
1239
- const [detail, setDetail] = useState(null);
1240
- const [error, setError] = useState(null);
1241
- const [copied, setCopied] = useState(false);
1242
- const [editing, setEditing] = useState(false);
1243
- const [editBody, setEditBody] = useState("");
1244
- const [savingEdit, setSavingEdit] = useState(false);
1245
- const [editError, setEditError] = useState(false);
1246
- const [composeBody, setComposeBody] = useState("");
1247
- const [sending, setSending] = useState(false);
1248
- const [closing, setClosing] = useState(false);
1249
- const [reopening, setReopening] = useState(false);
1250
- const [composeShots, setComposeShots] = useState([]);
1251
- const [attachError, setAttachError] = useState("");
1352
+ const [detail, setDetail] = useState2(null);
1353
+ const [error, setError] = useState2(null);
1354
+ const [copied, setCopied] = useState2(false);
1355
+ const [editing, setEditing] = useState2(false);
1356
+ const [editBody, setEditBody] = useState2("");
1357
+ const [savingEdit, setSavingEdit] = useState2(false);
1358
+ const [editError, setEditError] = useState2(false);
1359
+ const [composeBody, setComposeBody] = useState2("");
1360
+ const [sending, setSending] = useState2(false);
1361
+ const [closing, setClosing] = useState2(false);
1362
+ const [reopening, setReopening] = useState2(false);
1363
+ const [composeShots, setComposeShots] = useState2([]);
1364
+ const [attachError, setAttachError] = useState2("");
1252
1365
  const fileInputRef = useRef(null);
1253
1366
  const mountedRef = useRef(true);
1367
+ const urlPins = useRef(/* @__PURE__ */ new Map());
1368
+ const [, bumpPinTick] = useState2(0);
1369
+ const PIN_MAX_AGE_MS = 45 * 6e4;
1370
+ const pinUrl = (key, url) => {
1371
+ if (!url) return url;
1372
+ const pin = urlPins.current.get(key);
1373
+ if (pin && Date.now() - pin.at < PIN_MAX_AGE_MS) return pin.url;
1374
+ urlPins.current.set(key, { url, at: Date.now() });
1375
+ return url;
1376
+ };
1377
+ const unpinUrl = (key) => {
1378
+ urlPins.current.delete(key);
1379
+ bumpPinTick((t) => t + 1);
1380
+ };
1254
1381
  const shotsRef = useRef(composeShots);
1255
1382
  shotsRef.current = composeShots;
1256
1383
  useEffect(() => {
@@ -1421,13 +1548,46 @@ function ReportDetailView({
1421
1548
  }
1422
1549
  };
1423
1550
  if (!detail && !error) {
1424
- return /* @__PURE__ */ jsx2("div", { class: "mine-loading", children: strings["mine.loading"] });
1551
+ if (seed) {
1552
+ const seedPill = /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${seed.status}`, children: strings[`status.${seed.status}`] ?? seed.status });
1553
+ return /* @__PURE__ */ jsxs3("div", { class: `report-detail variant-${variant} is-seeded`, children: [
1554
+ variant === "modal" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header", children: [
1555
+ /* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
1556
+ "\u2190 ",
1557
+ strings["detail.back"]
1558
+ ] }),
1559
+ seedPill
1560
+ ] }),
1561
+ variant === "board" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header report-detail-header--board", children: [
1562
+ /* @__PURE__ */ jsxs3(
1563
+ "button",
1564
+ {
1565
+ type: "button",
1566
+ class: "btn btn--ghost report-detail-back",
1567
+ onClick: onBack,
1568
+ "aria-label": strings["board.back"],
1569
+ children: [
1570
+ "\u2190 ",
1571
+ strings["board.back"]
1572
+ ]
1573
+ }
1574
+ ),
1575
+ seedPill
1576
+ ] }),
1577
+ /* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
1578
+ /* @__PURE__ */ jsx3("div", { class: "report-detail-description-row", children: /* @__PURE__ */ jsx3("p", { class: "report-detail-description", children: linkifySeq(seed.description, onOpenSeq) }) }),
1579
+ /* @__PURE__ */ jsx3("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
1580
+ /* @__PURE__ */ jsx3("div", { class: "mine-loading", children: strings["mine.loading"] })
1581
+ ] })
1582
+ ] });
1583
+ }
1584
+ return /* @__PURE__ */ jsx3("div", { class: "mine-loading", children: strings["mine.loading"] });
1425
1585
  }
1426
1586
  if (!detail) {
1427
1587
  if (error && error !== "load_failed") {
1428
1588
  return /* @__PURE__ */ jsxs3("div", { class: "report-detail-empty-state", role: "alert", children: [
1429
- /* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-body", children: error }),
1430
- /* @__PURE__ */ jsx2("button", { type: "button", class: "btn", onClick: () => void fetchDetail(), children: strings["board.retry"] }),
1589
+ /* @__PURE__ */ jsx3("p", { class: "report-detail-empty-state-body", children: error }),
1590
+ /* @__PURE__ */ jsx3("button", { type: "button", class: "btn", onClick: () => void fetchDetail(), children: strings["board.retry"] }),
1431
1591
  /* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
1432
1592
  "\u2190 ",
1433
1593
  strings["detail.load_failed.cta"]
@@ -1435,8 +1595,8 @@ function ReportDetailView({
1435
1595
  ] });
1436
1596
  }
1437
1597
  return /* @__PURE__ */ jsxs3("div", { class: "report-detail-empty-state", role: "alert", children: [
1438
- /* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-title", children: strings["detail.load_failed.title"] }),
1439
- /* @__PURE__ */ jsx2("p", { class: "report-detail-empty-state-body", children: strings["detail.load_failed.body"] }),
1598
+ /* @__PURE__ */ jsx3("p", { class: "report-detail-empty-state-title", children: strings["detail.load_failed.title"] }),
1599
+ /* @__PURE__ */ jsx3("p", { class: "report-detail-empty-state-body", children: strings["detail.load_failed.body"] }),
1440
1600
  /* @__PURE__ */ jsxs3("button", { type: "button", class: "btn", onClick: onBack, children: [
1441
1601
  "\u2190 ",
1442
1602
  strings["detail.load_failed.cta"]
@@ -1453,7 +1613,7 @@ function ReportDetailView({
1453
1613
  "\u2190 ",
1454
1614
  strings["detail.back"]
1455
1615
  ] }),
1456
- /* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
1616
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
1457
1617
  ] }),
1458
1618
  variant === "board" && /* @__PURE__ */ jsxs3("div", { class: "report-detail-header report-detail-header--board", children: [
1459
1619
  /* @__PURE__ */ jsxs3(
@@ -1469,12 +1629,12 @@ function ReportDetailView({
1469
1629
  ]
1470
1630
  }
1471
1631
  ),
1472
- /* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
1632
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${detail.status}`, children: strings[`status.${detail.status}`] ?? detail.status })
1473
1633
  ] }),
1474
1634
  /* @__PURE__ */ jsxs3("div", { class: "report-detail-body", children: [
1475
- /* @__PURE__ */ jsx2(ContextBlock, { detail, strings }),
1635
+ /* @__PURE__ */ jsx3(ContextBlock, { detail, strings }),
1476
1636
  editing ? /* @__PURE__ */ jsxs3("div", { class: "report-detail-edit", children: [
1477
- /* @__PURE__ */ jsx2(
1637
+ /* @__PURE__ */ jsx3(
1478
1638
  "textarea",
1479
1639
  {
1480
1640
  class: "report-detail-edit-input",
@@ -1486,9 +1646,9 @@ function ReportDetailView({
1486
1646
  disabled: savingEdit
1487
1647
  }
1488
1648
  ),
1489
- editError && /* @__PURE__ */ jsx2("p", { class: "error", role: "alert", children: strings["detail.edit_failed"] }),
1649
+ editError && /* @__PURE__ */ jsx3("p", { class: "error", role: "alert", children: strings["detail.edit_failed"] }),
1490
1650
  /* @__PURE__ */ jsxs3("div", { class: "report-detail-edit-actions", children: [
1491
- /* @__PURE__ */ jsx2(
1651
+ /* @__PURE__ */ jsx3(
1492
1652
  "button",
1493
1653
  {
1494
1654
  type: "button",
@@ -1498,7 +1658,7 @@ function ReportDetailView({
1498
1658
  children: strings["detail.edit_cancel"]
1499
1659
  }
1500
1660
  ),
1501
- /* @__PURE__ */ jsx2(
1661
+ /* @__PURE__ */ jsx3(
1502
1662
  "button",
1503
1663
  {
1504
1664
  type: "button",
@@ -1510,8 +1670,8 @@ function ReportDetailView({
1510
1670
  )
1511
1671
  ] })
1512
1672
  ] }) : /* @__PURE__ */ jsxs3("div", { class: "report-detail-description-row", children: [
1513
- /* @__PURE__ */ jsx2("p", { class: "report-detail-description", children: linkifySeq(detail.description, onOpenSeq) }),
1514
- isMine && /* @__PURE__ */ jsx2(
1673
+ /* @__PURE__ */ jsx3("p", { class: "report-detail-description", children: linkifySeq(detail.description, onOpenSeq) }),
1674
+ isMine && /* @__PURE__ */ jsx3(
1515
1675
  "button",
1516
1676
  {
1517
1677
  type: "button",
@@ -1521,7 +1681,7 @@ function ReportDetailView({
1521
1681
  }
1522
1682
  )
1523
1683
  ] }),
1524
- buildPermalink && /* @__PURE__ */ jsx2("div", { class: "report-detail-permalink", children: /* @__PURE__ */ jsx2(
1684
+ buildPermalink && /* @__PURE__ */ jsx3("div", { class: "report-detail-permalink", children: /* @__PURE__ */ jsx3(
1525
1685
  "button",
1526
1686
  {
1527
1687
  type: "button",
@@ -1530,32 +1690,33 @@ function ReportDetailView({
1530
1690
  children: copied ? strings["detail.copied"] : strings["detail.copy_link"]
1531
1691
  }
1532
1692
  ) }),
1533
- (detail.screenshots?.length ? detail.screenshots.map((s) => s.url) : [detail.screenshot_url]).filter((url) => Boolean(url)).map((url) => {
1693
+ (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
1694
  const safeHref = safeExternalHref(url);
1535
- return safeHref ? /* @__PURE__ */ jsx2(
1695
+ return safeHref ? /* @__PURE__ */ jsx3(
1536
1696
  "a",
1537
1697
  {
1538
1698
  class: "report-detail-screenshot",
1539
1699
  href: safeHref,
1540
1700
  target: "_blank",
1541
1701
  rel: "noopener noreferrer",
1542
- children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" })
1702
+ children: /* @__PURE__ */ jsx3("img", { src: url, alt: "", loading: "lazy", onError: () => unpinUrl(pinKey) })
1543
1703
  }
1544
- ) : /* @__PURE__ */ jsx2("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx2("img", { src: url, alt: "", loading: "lazy" }) });
1704
+ ) : /* @__PURE__ */ jsx3("div", { class: "report-detail-screenshot", children: /* @__PURE__ */ jsx3("img", { src: url, alt: "", loading: "lazy", onError: () => unpinUrl(pinKey) }) });
1545
1705
  }),
1546
- /* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
1547
- 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(
1706
+ /* @__PURE__ */ jsx3("h3", { class: "report-detail-section", children: strings["detail.thread"] }),
1707
+ detail.comments.length === 0 ? /* @__PURE__ */ jsx3("p", { class: "report-detail-empty", children: strings["detail.no_replies"] }) : /* @__PURE__ */ jsx3("ul", { class: "report-comments", children: detail.comments.map((c) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
1548
1708
  CommentBubble,
1549
1709
  {
1550
1710
  comment: c,
1551
1711
  strings,
1552
- ...onOpenSeq && { onOpenSeq }
1712
+ ...onOpenSeq && { onOpenSeq },
1713
+ stableUrl: (key, url) => pinUrl(key, url) ?? url
1553
1714
  }
1554
1715
  ) })) }),
1555
- detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx2(StatusHistorySection, { rows: detail.status_history, strings }),
1556
- detail.technical_context && /* @__PURE__ */ jsx2(TechnicalContextDrawer, { ctx: detail.technical_context, strings }),
1716
+ detail.status_history && detail.status_history.length > 0 && /* @__PURE__ */ jsx3(StatusHistorySection, { rows: detail.status_history, strings }),
1717
+ detail.technical_context && /* @__PURE__ */ jsx3(TechnicalContextDrawer, { ctx: detail.technical_context, strings }),
1557
1718
  showComposeBox ? /* @__PURE__ */ jsxs3("div", { class: "report-compose", children: [
1558
- /* @__PURE__ */ jsx2(
1719
+ /* @__PURE__ */ jsx3(
1559
1720
  "textarea",
1560
1721
  {
1561
1722
  value: composeBody,
@@ -1564,9 +1725,9 @@ function ReportDetailView({
1564
1725
  disabled: sending
1565
1726
  }
1566
1727
  ),
1567
- composeShots.length > 0 && /* @__PURE__ */ jsx2("div", { class: "compose-attachments", children: composeShots.map((s, i) => /* @__PURE__ */ jsxs3("div", { class: "compose-attachment", children: [
1568
- /* @__PURE__ */ jsx2("img", { src: s.preview, alt: strings["detail.attachment_alt"] }),
1569
- /* @__PURE__ */ jsx2(
1728
+ composeShots.length > 0 && /* @__PURE__ */ jsx3("div", { class: "compose-attachments", children: composeShots.map((s, i) => /* @__PURE__ */ jsxs3("div", { class: "compose-attachment", children: [
1729
+ /* @__PURE__ */ jsx3("img", { src: s.preview, alt: strings["detail.attachment_alt"] }),
1730
+ /* @__PURE__ */ jsx3(
1570
1731
  "button",
1571
1732
  {
1572
1733
  type: "button",
@@ -1578,8 +1739,8 @@ function ReportDetailView({
1578
1739
  }
1579
1740
  )
1580
1741
  ] }, s.preview)) }),
1581
- attachError && /* @__PURE__ */ jsx2("p", { class: "compose-attach-error", role: "alert", children: attachError }),
1582
- /* @__PURE__ */ jsx2(
1742
+ attachError && /* @__PURE__ */ jsx3("p", { class: "compose-attach-error", role: "alert", children: attachError }),
1743
+ /* @__PURE__ */ jsx3(
1583
1744
  "input",
1584
1745
  {
1585
1746
  ref: fileInputRef,
@@ -1592,7 +1753,7 @@ function ReportDetailView({
1592
1753
  }
1593
1754
  ),
1594
1755
  /* @__PURE__ */ jsxs3("div", { class: "report-compose-actions", children: [
1595
- /* @__PURE__ */ jsx2(
1756
+ /* @__PURE__ */ jsx3(
1596
1757
  "button",
1597
1758
  {
1598
1759
  type: "button",
@@ -1602,7 +1763,7 @@ function ReportDetailView({
1602
1763
  children: strings["detail.attach_add"]
1603
1764
  }
1604
1765
  ),
1605
- showCloseCta && /* @__PURE__ */ jsx2(
1766
+ showCloseCta && /* @__PURE__ */ jsx3(
1606
1767
  "button",
1607
1768
  {
1608
1769
  type: "button",
@@ -1612,7 +1773,7 @@ function ReportDetailView({
1612
1773
  children: closing ? strings["detail.close_busy"] : strings["detail.close_cta"]
1613
1774
  }
1614
1775
  ),
1615
- showCloseCta && /* @__PURE__ */ jsx2(
1776
+ showCloseCta && /* @__PURE__ */ jsx3(
1616
1777
  "button",
1617
1778
  {
1618
1779
  type: "button",
@@ -1622,7 +1783,7 @@ function ReportDetailView({
1622
1783
  children: reopening ? strings["detail.reopen_busy"] : strings["detail.reopen_cta"]
1623
1784
  }
1624
1785
  ),
1625
- /* @__PURE__ */ jsx2(
1786
+ /* @__PURE__ */ jsx3(
1626
1787
  "button",
1627
1788
  {
1628
1789
  type: "button",
@@ -1638,9 +1799,9 @@ function ReportDetailView({
1638
1799
  // to the submitter, so we hide the compose box entirely (action
1639
1800
  // is genuinely unavailable) and surface a short notice so the
1640
1801
  // viewer understands why.
1641
- /* @__PURE__ */ jsx2("p", { class: "report-detail-teammate-notice", role: "note", children: strings["detail.teammate_notice"] })
1802
+ /* @__PURE__ */ jsx3("p", { class: "report-detail-teammate-notice", role: "note", children: strings["detail.teammate_notice"] })
1642
1803
  ),
1643
- error && /* @__PURE__ */ jsx2("div", { class: "error", children: error })
1804
+ error && /* @__PURE__ */ jsx3("div", { class: "error", children: error })
1644
1805
  ] })
1645
1806
  ] });
1646
1807
  }
@@ -1653,19 +1814,19 @@ function ContextBlock({ detail, strings }) {
1653
1814
  const captureLabel = strings[captureKey] ?? detail.capture_method;
1654
1815
  return /* @__PURE__ */ jsxs3("div", { class: "report-detail-context", children: [
1655
1816
  /* @__PURE__ */ jsxs3("div", { class: "report-detail-context-pills", children: [
1656
- /* @__PURE__ */ jsx2("span", { class: "pill pill-type", children: strings[`type.${detail.feedback_type}`] }),
1657
- /* @__PURE__ */ jsx2("span", { class: `pill pill-severity pill-severity--${detail.severity}`, children: strings[`severity.${detail.severity}`] }),
1658
- /* @__PURE__ */ jsx2("span", { class: "pill pill-capture", children: captureLabel })
1817
+ /* @__PURE__ */ jsx3("span", { class: "pill pill-type", children: strings[`type.${detail.feedback_type}`] }),
1818
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-severity pill-severity--${detail.severity}`, children: strings[`severity.${detail.severity}`] }),
1819
+ /* @__PURE__ */ jsx3("span", { class: "pill pill-capture", children: captureLabel })
1659
1820
  ] }),
1660
1821
  /* @__PURE__ */ jsxs3("div", { class: "report-detail-context-line", children: [
1661
- /* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.submitted_at"] }),
1662
- /* @__PURE__ */ jsx2("span", { children: formatSubmittedAt(detail.created_at) })
1822
+ /* @__PURE__ */ jsx3("span", { class: "report-detail-context-label", children: strings["detail.context.submitted_at"] }),
1823
+ /* @__PURE__ */ jsx3("span", { children: formatSubmittedAt(detail.created_at) })
1663
1824
  ] }),
1664
1825
  detail.page_url && (() => {
1665
1826
  const safeHref = safeExternalHref(detail.page_url);
1666
1827
  return /* @__PURE__ */ jsxs3("div", { class: "report-detail-context-line", title: detail.page_url, children: [
1667
- /* @__PURE__ */ jsx2("span", { class: "report-detail-context-label", children: strings["detail.context.page"] }),
1668
- safeHref ? /* @__PURE__ */ jsx2(
1828
+ /* @__PURE__ */ jsx3("span", { class: "report-detail-context-label", children: strings["detail.context.page"] }),
1829
+ safeHref ? /* @__PURE__ */ jsx3(
1669
1830
  "a",
1670
1831
  {
1671
1832
  class: "report-detail-context-url",
@@ -1674,7 +1835,7 @@ function ContextBlock({ detail, strings }) {
1674
1835
  rel: "noopener noreferrer",
1675
1836
  children: truncateUrl(detail.page_url, 64)
1676
1837
  }
1677
- ) : /* @__PURE__ */ jsx2("span", { class: "report-detail-context-url", children: truncateUrl(detail.page_url, 64) })
1838
+ ) : /* @__PURE__ */ jsx3("span", { class: "report-detail-context-url", children: truncateUrl(detail.page_url, 64) })
1678
1839
  ] });
1679
1840
  })()
1680
1841
  ] });
@@ -1701,12 +1862,12 @@ function TechnicalContextDrawer({ ctx, strings }) {
1701
1862
  const errorCount = errors.length;
1702
1863
  const summary = errorCount > 0 ? `${strings["detail.tech.title"]} \xB7 ${errorCount} ${errorCount === 1 ? strings["detail.tech.errors_one"] : strings["detail.tech.errors_many"]}` : strings["detail.tech.title"];
1703
1864
  return /* @__PURE__ */ jsxs3("details", { class: "report-detail-tech", children: [
1704
- /* @__PURE__ */ jsx2("summary", { children: summary }),
1865
+ /* @__PURE__ */ jsx3("summary", { children: summary }),
1705
1866
  /* @__PURE__ */ jsxs3("div", { class: "tech-body", children: [
1706
- device && /* @__PURE__ */ jsx2(DeviceSection, { device, strings }),
1707
- errors.length > 0 && /* @__PURE__ */ jsx2(ErrorsSection, { errors, strings }),
1708
- consoleLogs.length > 0 && /* @__PURE__ */ jsx2(ConsoleSection, { logs: consoleLogs, strings }),
1709
- network.length > 0 && /* @__PURE__ */ jsx2(NetworkSection, { rows: network, strings })
1867
+ device && /* @__PURE__ */ jsx3(DeviceSection, { device, strings }),
1868
+ errors.length > 0 && /* @__PURE__ */ jsx3(ErrorsSection, { errors, strings }),
1869
+ consoleLogs.length > 0 && /* @__PURE__ */ jsx3(ConsoleSection, { logs: consoleLogs, strings }),
1870
+ network.length > 0 && /* @__PURE__ */ jsx3(NetworkSection, { rows: network, strings })
1710
1871
  ] })
1711
1872
  ] });
1712
1873
  }
@@ -1728,10 +1889,10 @@ function DeviceSection({
1728
1889
  if (device?.connection) parts.push({ label: strings["detail.tech.device.connection"], value: device.connection });
1729
1890
  if (parts.length === 0) return null;
1730
1891
  return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
1731
- /* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.device"] }),
1732
- /* @__PURE__ */ jsx2("dl", { class: "tech-device", children: parts.map((p) => /* @__PURE__ */ jsxs3(Fragment, { children: [
1733
- /* @__PURE__ */ jsx2("dt", { children: p.label }),
1734
- /* @__PURE__ */ jsx2("dd", { children: p.value })
1892
+ /* @__PURE__ */ jsx3("h4", { children: strings["detail.tech.device"] }),
1893
+ /* @__PURE__ */ jsx3("dl", { class: "tech-device", children: parts.map((p) => /* @__PURE__ */ jsxs3(Fragment, { children: [
1894
+ /* @__PURE__ */ jsx3("dt", { children: p.label }),
1895
+ /* @__PURE__ */ jsx3("dd", { children: p.value })
1735
1896
  ] })) })
1736
1897
  ] });
1737
1898
  }
@@ -1740,10 +1901,10 @@ function ErrorsSection({
1740
1901
  strings
1741
1902
  }) {
1742
1903
  return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
1743
- /* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.errors"] }),
1744
- /* @__PURE__ */ jsx2("ul", { class: "tech-errors", children: errors.map((e) => /* @__PURE__ */ jsxs3("li", { children: [
1745
- /* @__PURE__ */ jsx2("div", { class: "tech-errors-msg", children: e.message }),
1746
- e.stack && /* @__PURE__ */ jsx2("pre", { class: "tech-errors-stack", children: truncateStack(e.stack) })
1904
+ /* @__PURE__ */ jsx3("h4", { children: strings["detail.tech.errors"] }),
1905
+ /* @__PURE__ */ jsx3("ul", { class: "tech-errors", children: errors.map((e) => /* @__PURE__ */ jsxs3("li", { children: [
1906
+ /* @__PURE__ */ jsx3("div", { class: "tech-errors-msg", children: e.message }),
1907
+ e.stack && /* @__PURE__ */ jsx3("pre", { class: "tech-errors-stack", children: truncateStack(e.stack) })
1747
1908
  ] })) })
1748
1909
  ] });
1749
1910
  }
@@ -1752,10 +1913,10 @@ function ConsoleSection({
1752
1913
  strings
1753
1914
  }) {
1754
1915
  return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
1755
- /* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.console"] }),
1756
- /* @__PURE__ */ jsx2("ul", { class: "tech-console", children: logs.map((l) => /* @__PURE__ */ jsxs3("li", { class: `tech-console-row tech-console-row--${l.level}`, children: [
1757
- /* @__PURE__ */ jsx2("span", { class: "tech-console-level", children: l.level }),
1758
- /* @__PURE__ */ jsx2("span", { class: "tech-console-msg", children: l.message })
1916
+ /* @__PURE__ */ jsx3("h4", { children: strings["detail.tech.console"] }),
1917
+ /* @__PURE__ */ jsx3("ul", { class: "tech-console", children: logs.map((l) => /* @__PURE__ */ jsxs3("li", { class: `tech-console-row tech-console-row--${l.level}`, children: [
1918
+ /* @__PURE__ */ jsx3("span", { class: "tech-console-level", children: l.level }),
1919
+ /* @__PURE__ */ jsx3("span", { class: "tech-console-msg", children: l.message })
1759
1920
  ] })) })
1760
1921
  ] });
1761
1922
  }
@@ -1764,13 +1925,13 @@ function NetworkSection({
1764
1925
  strings
1765
1926
  }) {
1766
1927
  return /* @__PURE__ */ jsxs3("div", { class: "tech-section", children: [
1767
- /* @__PURE__ */ jsx2("h4", { children: strings["detail.tech.network"] }),
1768
- /* @__PURE__ */ jsx2("ul", { class: "tech-network", children: rows.map((n) => {
1928
+ /* @__PURE__ */ jsx3("h4", { children: strings["detail.tech.network"] }),
1929
+ /* @__PURE__ */ jsx3("ul", { class: "tech-network", children: rows.map((n) => {
1769
1930
  const failed = n.status >= 400 || n.status === 0;
1770
1931
  return /* @__PURE__ */ jsxs3("li", { class: `tech-network-row${failed ? " tech-network-row--fail" : ""}`, children: [
1771
- /* @__PURE__ */ jsx2("span", { class: "tech-network-status", children: n.status || "\u2014" }),
1772
- /* @__PURE__ */ jsx2("span", { class: "tech-network-method", children: n.method }),
1773
- /* @__PURE__ */ jsx2("span", { class: "tech-network-url", title: n.url, children: truncateUrl(n.url, 56) }),
1932
+ /* @__PURE__ */ jsx3("span", { class: "tech-network-status", children: n.status || "\u2014" }),
1933
+ /* @__PURE__ */ jsx3("span", { class: "tech-network-method", children: n.method }),
1934
+ /* @__PURE__ */ jsx3("span", { class: "tech-network-url", title: n.url, children: truncateUrl(n.url, 56) }),
1774
1935
  /* @__PURE__ */ jsxs3("span", { class: "tech-network-time", children: [
1775
1936
  Math.round(n.durationMs),
1776
1937
  "ms"
@@ -1786,26 +1947,26 @@ function truncateStack(stack) {
1786
1947
  }
1787
1948
  function StatusHistorySection({ rows, strings }) {
1788
1949
  return /* @__PURE__ */ jsxs3("div", { class: "report-detail-history", children: [
1789
- /* @__PURE__ */ jsx2("h3", { class: "report-detail-section", children: strings["detail.history"] }),
1790
- /* @__PURE__ */ jsx2("ol", { class: "status-history", children: rows.map((r) => {
1950
+ /* @__PURE__ */ jsx3("h3", { class: "report-detail-section", children: strings["detail.history"] }),
1951
+ /* @__PURE__ */ jsx3("ol", { class: "status-history", children: rows.map((r) => {
1791
1952
  const from = strings[`status.${r.from_status}`] ?? r.from_status;
1792
1953
  const to = strings[`status.${r.to_status}`] ?? r.to_status;
1793
1954
  const sourceKey = r.changed_by_source === "mcp" ? "detail.author.mcp" : r.changed_by_source === "system" ? "detail.author.system" : "detail.author.staff";
1794
1955
  return /* @__PURE__ */ jsxs3("li", { class: "status-history-row", children: [
1795
- /* @__PURE__ */ jsx2("span", { class: "status-history-time", children: formatSubmittedAt(r.created_at) }),
1956
+ /* @__PURE__ */ jsx3("span", { class: "status-history-time", children: formatSubmittedAt(r.created_at) }),
1796
1957
  /* @__PURE__ */ jsxs3("span", { class: "status-history-transition", children: [
1797
- /* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${r.from_status}`, children: from }),
1798
- /* @__PURE__ */ jsx2("span", { class: "status-history-arrow", "aria-hidden": "true", children: "\u2192" }),
1799
- /* @__PURE__ */ jsx2("span", { class: `pill pill-status pill-status--${r.to_status}`, children: to })
1958
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${r.from_status}`, children: from }),
1959
+ /* @__PURE__ */ jsx3("span", { class: "status-history-arrow", "aria-hidden": "true", children: "\u2192" }),
1960
+ /* @__PURE__ */ jsx3("span", { class: `pill pill-status pill-status--${r.to_status}`, children: to })
1800
1961
  ] }),
1801
- /* @__PURE__ */ jsx2("span", { class: `status-history-source status-history-source--${r.changed_by_source}`, children: strings[sourceKey] })
1962
+ /* @__PURE__ */ jsx3("span", { class: `status-history-source status-history-source--${r.changed_by_source}`, children: strings[sourceKey] })
1802
1963
  ] });
1803
1964
  }) })
1804
1965
  ] });
1805
1966
  }
1806
1967
 
1807
1968
  // src/widget/BoardView.tsx
1808
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs4 } from "preact/jsx-runtime";
1969
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "preact/jsx-runtime";
1809
1970
  var POLL_MS2 = 3e4;
1810
1971
  var SEARCH_DEBOUNCE_MS = 300;
1811
1972
  var SORT_OPTIONS = ["recent", "oldest", "updated", "severity", "status"];
@@ -1825,19 +1986,22 @@ function BoardView({
1825
1986
  getCurrentPage,
1826
1987
  initialSelectedId
1827
1988
  }) {
1828
- const [filters, setFilters] = useState2(() => loadBoardView(externalId));
1829
- const [thisPage, setThisPage] = useState2(true);
1989
+ const [filters, setFilters] = useState3(() => loadBoardView(externalId));
1990
+ const [thisPage, setThisPage] = useState3(true);
1830
1991
  useEffect2(() => {
1831
1992
  saveBoardView(externalId, filters);
1832
1993
  }, [externalId, filtersHash(filters)]);
1833
- const [page, setPage] = useState2(null);
1834
- const [kpis, setKpis] = useState2(null);
1835
- const [loading, setLoading] = useState2(true);
1836
- const [error, setError] = useState2(null);
1837
- const [selectedId, setSelectedId] = useState2(initialSelectedId ?? null);
1838
- const [detailKey, setDetailKey] = useState2(0);
1839
- const [reloadTick, setReloadTick] = useState2(0);
1840
- const [qDraft, setQDraft] = useState2(filters.q ?? "");
1994
+ const [page, setPage] = useState3(null);
1995
+ const [kpis, setKpis] = useState3(null);
1996
+ const [loading, setLoading] = useState3(true);
1997
+ const [stale, setStale] = useState3(false);
1998
+ const [error, setError] = useState3(null);
1999
+ const [selectedId, setSelectedId] = useState3(initialSelectedId ?? null);
2000
+ const [detailKey, setDetailKey] = useState3(0);
2001
+ const [reloadTick, setReloadTick] = useState3(0);
2002
+ const [extra, setExtra] = useState3(null);
2003
+ const [loadingMore, setLoadingMore] = useState3(false);
2004
+ const [qDraft, setQDraft] = useState3(filters.q ?? "");
1841
2005
  const debouncedQ = useDebouncedValue(qDraft, SEARCH_DEBOUNCE_MS);
1842
2006
  useEffect2(() => {
1843
2007
  setFilters((f) => {
@@ -1866,18 +2030,36 @@ function BoardView({
1866
2030
  }, [filtersHash(filters), thisPage, reloadTick]);
1867
2031
  useEffect2(() => {
1868
2032
  let cancelled = false;
1869
- setPage(null);
1870
- setKpis(null);
1871
- setLoading(true);
2033
+ const controller = new AbortController();
2034
+ const cacheKey = boardCacheKey(externalId, JSON.stringify(fetchFilters));
2035
+ const cached = readBoardCache(cacheKey);
2036
+ if (cached) {
2037
+ setPage(cached.page);
2038
+ setKpis(cached.kpis);
2039
+ setStale(false);
2040
+ setLoading(false);
2041
+ } else {
2042
+ setPage(
2043
+ (prev) => prev ? { ...prev, results: applyLocalFilters(prev.results, fetchFilters) } : null
2044
+ );
2045
+ setStale(true);
2046
+ setLoading(true);
2047
+ }
2048
+ setExtra(null);
2049
+ setLoadingMore(false);
1872
2050
  const poll = startPoll(async () => {
1873
2051
  try {
1874
- const [list, k] = await Promise.all([
1875
- api.listBoard(externalId, fetchFilters),
1876
- api.fetchBoardKpis(externalId, fetchFilters)
1877
- ]);
2052
+ const list = await api.listBoard(externalId, fetchFilters, {
2053
+ signal: controller.signal
2054
+ });
2055
+ const k = list.kpis ?? await api.fetchBoardKpis(externalId, fetchFilters, {
2056
+ signal: controller.signal
2057
+ });
1878
2058
  if (cancelled) return true;
2059
+ writeBoardCache(cacheKey, list, k);
1879
2060
  setPage(list);
1880
2061
  setKpis(k);
2062
+ setStale(false);
1881
2063
  setError(null);
1882
2064
  return true;
1883
2065
  } catch (e) {
@@ -1891,19 +2073,43 @@ function BoardView({
1891
2073
  return () => {
1892
2074
  cancelled = true;
1893
2075
  poll.stop();
2076
+ controller.abort();
1894
2077
  };
1895
2078
  }, [api, externalId, JSON.stringify(fetchFilters)]);
2079
+ const visibleRows = useMemo(() => {
2080
+ if (!page) return [];
2081
+ if (!extra || extra.rows.length === 0) return page.results;
2082
+ const seen = new Set(page.results.map((r) => r.id));
2083
+ return [...page.results, ...extra.rows.filter((r) => !seen.has(r.id))];
2084
+ }, [page, extra]);
2085
+ const hasMore = extra ? extra.hasMore : Boolean(page?.next);
2086
+ const loadMore = async () => {
2087
+ if (loadingMore || !page) return;
2088
+ const pageNum = extra ? extra.nextPage : 2;
2089
+ setLoadingMore(true);
2090
+ try {
2091
+ const res = await api.listBoard(externalId, { ...fetchFilters, page: pageNum });
2092
+ setExtra((prev) => ({
2093
+ rows: [...prev?.rows ?? [], ...res.results],
2094
+ nextPage: pageNum + 1,
2095
+ hasMore: res.next !== null
2096
+ }));
2097
+ } catch {
2098
+ } finally {
2099
+ setLoadingMore(false);
2100
+ }
2101
+ };
1896
2102
  const selectedRow = useMemo(() => {
1897
- if (!selectedId || !page) return null;
1898
- return page.results.find((r) => r.id === selectedId) ?? null;
1899
- }, [selectedId, page]);
2103
+ if (!selectedId) return null;
2104
+ return visibleRows.find((r) => r.id === selectedId) ?? null;
2105
+ }, [selectedId, visibleRows]);
1900
2106
  const onPickRow = (row) => {
1901
2107
  setSelectedId(row.id);
1902
2108
  setDetailKey((k) => k + 1);
1903
2109
  };
1904
- return /* @__PURE__ */ jsxs4("div", { class: "board-view", children: [
1905
- /* @__PURE__ */ jsx3(BoardHeader, { strings, kpis, thisPage }),
1906
- /* @__PURE__ */ jsx3(
2110
+ return /* @__PURE__ */ jsxs4("div", { class: `board-view ${stale ? "is-stale" : ""}`, children: [
2111
+ /* @__PURE__ */ jsx4(BoardHeader, { strings, kpis, thisPage }),
2112
+ /* @__PURE__ */ jsx4(
1907
2113
  BoardFilters,
1908
2114
  {
1909
2115
  filters,
@@ -1917,10 +2123,10 @@ function BoardView({
1917
2123
  }
1918
2124
  ),
1919
2125
  /* @__PURE__ */ jsxs4("div", { class: "board-body", children: [
1920
- /* @__PURE__ */ jsxs4("div", { class: "board-list-wrap", "aria-busy": loading && !page, children: [
2126
+ /* @__PURE__ */ jsxs4("div", { class: "board-list-wrap", "aria-busy": loading, children: [
1921
2127
  error && /* @__PURE__ */ jsxs4("div", { class: "board-error", role: "alert", children: [
1922
- /* @__PURE__ */ jsx3("span", { children: error }),
1923
- /* @__PURE__ */ jsx3(
2128
+ /* @__PURE__ */ jsx4("span", { children: error }),
2129
+ /* @__PURE__ */ jsx4(
1924
2130
  "button",
1925
2131
  {
1926
2132
  type: "button",
@@ -1932,10 +2138,11 @@ function BoardView({
1932
2138
  },
1933
2139
  children: strings["board.retry"]
1934
2140
  }
1935
- )
2141
+ ),
2142
+ /* @__PURE__ */ jsx4(DiagnosticHint, { api, strings, host: api.endpointHost })
1936
2143
  ] }),
1937
- !error && loading && !page && /* @__PURE__ */ jsx3(BoardListSkeleton, {}),
1938
- !error && page && page.results.length === 0 && !loading && /* @__PURE__ */ jsx3(
2144
+ !error && loading && !page && /* @__PURE__ */ jsx4(BoardListSkeleton, {}),
2145
+ !error && page && visibleRows.length === 0 && (!loading || stale) && /* @__PURE__ */ jsx4(
1939
2146
  BoardEmpty,
1940
2147
  {
1941
2148
  strings,
@@ -1943,27 +2150,40 @@ function BoardView({
1943
2150
  onAllPages: () => setThisPage(false)
1944
2151
  }
1945
2152
  ),
1946
- !error && page && page.results.length > 0 && /* @__PURE__ */ jsx3(
1947
- BoardList,
1948
- {
1949
- rows: page.results,
1950
- selectedId,
1951
- onPick: onPickRow,
1952
- strings,
1953
- total: page.count,
1954
- thisPage
1955
- }
1956
- )
2153
+ !error && page && visibleRows.length > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2154
+ /* @__PURE__ */ jsx4(
2155
+ BoardList,
2156
+ {
2157
+ rows: visibleRows,
2158
+ selectedId,
2159
+ onPick: onPickRow,
2160
+ strings,
2161
+ total: page.count,
2162
+ thisPage
2163
+ }
2164
+ ),
2165
+ hasMore && !stale && /* @__PURE__ */ jsx4(
2166
+ "button",
2167
+ {
2168
+ type: "button",
2169
+ class: "btn btn--ghost board-load-more",
2170
+ onClick: () => void loadMore(),
2171
+ disabled: loadingMore,
2172
+ children: strings["board.list.loadMore"]
2173
+ }
2174
+ )
2175
+ ] })
1957
2176
  ] }),
1958
- /* @__PURE__ */ jsx3("div", { class: `board-detail-wrap ${selectedRow ? "has-selection" : ""}`, children: selectedRow ? /* @__PURE__ */ jsx3(
2177
+ /* @__PURE__ */ jsx4("div", { class: `board-detail-wrap ${selectedId ? "has-selection" : ""}`, children: selectedId ? /* @__PURE__ */ jsx4(
1959
2178
  ReportDetailView,
1960
2179
  {
1961
2180
  api,
1962
2181
  externalId,
1963
- reportId: selectedRow.id,
2182
+ reportId: selectedId,
1964
2183
  strings,
1965
2184
  onBack: () => setSelectedId(null),
1966
- canModerate: selectedRow.can_moderate ?? selectedRow.is_mine,
2185
+ canModerate: selectedRow ? selectedRow.can_moderate ?? selectedRow.is_mine : false,
2186
+ ...selectedRow ? { seed: selectedRow } : {},
1967
2187
  variant: "board",
1968
2188
  onOpenSeq: (seq) => {
1969
2189
  void api.getReportBySeq(seq, externalId).then((r) => {
@@ -1975,8 +2195,8 @@ function BoardView({
1975
2195
  },
1976
2196
  detailKey
1977
2197
  ) : /* @__PURE__ */ jsxs4("div", { class: "board-detail-empty", children: [
1978
- /* @__PURE__ */ jsx3(DetailEmptyIllustration, {}),
1979
- /* @__PURE__ */ jsx3("p", { children: strings["board.detail.empty"] })
2198
+ /* @__PURE__ */ jsx4(DetailEmptyIllustration, {}),
2199
+ /* @__PURE__ */ jsx4("p", { children: strings["board.detail.empty"] })
1980
2200
  ] }) })
1981
2201
  ] })
1982
2202
  ] });
@@ -1989,13 +2209,13 @@ function BoardHeader({
1989
2209
  const scopeLabel = thisPage ? strings["board.scope.onThisPage"] : kpis?.scope === "project" ? strings["board.scope.project"] : strings["board.scope.mine"];
1990
2210
  return /* @__PURE__ */ jsxs4("header", { class: "board-header", children: [
1991
2211
  /* @__PURE__ */ jsxs4("div", { class: "board-header-title", children: [
1992
- /* @__PURE__ */ jsx3("span", { class: "board-header-emoji", "aria-hidden": "true", children: "\u{1F4CB}" }),
2212
+ /* @__PURE__ */ jsx4("span", { class: "board-header-emoji", "aria-hidden": "true", children: "\u{1F4CB}" }),
1993
2213
  /* @__PURE__ */ jsxs4("div", { children: [
1994
- /* @__PURE__ */ jsx3("h2", { class: "board-header-h", children: strings["tab.board"] }),
1995
- /* @__PURE__ */ jsx3("p", { class: "board-header-sub", children: kpis ? `${kpis.total} \xB7 ${scopeLabel}` : scopeLabel })
2214
+ /* @__PURE__ */ jsx4("h2", { class: "board-header-h", children: strings["tab.board"] }),
2215
+ /* @__PURE__ */ jsx4("p", { class: "board-header-sub", children: kpis ? `${kpis.total} \xB7 ${scopeLabel}` : scopeLabel })
1996
2216
  ] })
1997
2217
  ] }),
1998
- /* @__PURE__ */ jsx3(BoardKpiStrip, { kpis, strings })
2218
+ /* @__PURE__ */ jsx4(BoardKpiStrip, { kpis, strings })
1999
2219
  ] });
2000
2220
  }
2001
2221
  function BoardKpiStrip({
@@ -2028,9 +2248,9 @@ function BoardKpiStrip({
2028
2248
  tone: "tone-rate"
2029
2249
  }
2030
2250
  ];
2031
- return /* @__PURE__ */ jsx3("div", { class: `board-kpi-strip ${kpis ? "is-ready" : "is-loading"}`, "aria-live": "polite", children: cells.map((c) => /* @__PURE__ */ jsxs4("div", { class: `board-kpi-card ${c.tone}`, children: [
2032
- /* @__PURE__ */ jsx3("div", { class: "board-kpi-value", children: c.value }),
2033
- /* @__PURE__ */ jsx3("div", { class: "board-kpi-label", children: c.label })
2251
+ return /* @__PURE__ */ jsx4("div", { class: `board-kpi-strip ${kpis ? "is-ready" : "is-loading"}`, "aria-live": "polite", children: cells.map((c) => /* @__PURE__ */ jsxs4("div", { class: `board-kpi-card ${c.tone}`, children: [
2252
+ /* @__PURE__ */ jsx4("div", { class: "board-kpi-value", children: c.value }),
2253
+ /* @__PURE__ */ jsx4("div", { class: "board-kpi-label", children: c.label })
2034
2254
  ] }, c.key)) });
2035
2255
  }
2036
2256
  function BoardFilters({
@@ -2083,7 +2303,7 @@ function BoardFilters({
2083
2303
  const clear = () => onChange({});
2084
2304
  return /* @__PURE__ */ jsxs4("div", { class: "board-filters", children: [
2085
2305
  /* @__PURE__ */ jsxs4("div", { class: "board-scope", role: "group", "aria-label": strings["board.scope.thisPage"], children: [
2086
- /* @__PURE__ */ jsx3(
2306
+ /* @__PURE__ */ jsx4(
2087
2307
  "button",
2088
2308
  {
2089
2309
  type: "button",
@@ -2093,7 +2313,7 @@ function BoardFilters({
2093
2313
  children: strings["board.scope.thisPage"]
2094
2314
  }
2095
2315
  ),
2096
- /* @__PURE__ */ jsx3(
2316
+ /* @__PURE__ */ jsx4(
2097
2317
  "button",
2098
2318
  {
2099
2319
  type: "button",
@@ -2104,14 +2324,14 @@ function BoardFilters({
2104
2324
  }
2105
2325
  )
2106
2326
  ] }),
2107
- /* @__PURE__ */ jsx3(
2327
+ /* @__PURE__ */ jsx4(
2108
2328
  "select",
2109
2329
  {
2110
2330
  class: "board-filter-select",
2111
2331
  "aria-label": strings["board.sort"],
2112
2332
  value: filters.ordering ?? "recent",
2113
2333
  onChange: (e) => setOrdering(e.target.value),
2114
- children: SORT_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o, children: strings[`board.sort.${o}`] }, o))
2334
+ children: SORT_OPTIONS.map((o) => /* @__PURE__ */ jsx4("option", { value: o, children: strings[`board.sort.${o}`] }, o))
2115
2335
  }
2116
2336
  ),
2117
2337
  /* @__PURE__ */ jsxs4(
@@ -2122,8 +2342,8 @@ function BoardFilters({
2122
2342
  value: filters.status?.[0] ?? "",
2123
2343
  onChange: (e) => setStatus(e.target.value),
2124
2344
  children: [
2125
- /* @__PURE__ */ jsx3("option", { value: "", children: strings["board.filter.status"] }),
2126
- STATUSES.map((s) => /* @__PURE__ */ jsx3("option", { value: s, children: strings[`board.kpi.${s}`] ?? s }, s))
2345
+ /* @__PURE__ */ jsx4("option", { value: "", children: strings["board.filter.status"] }),
2346
+ STATUSES.map((s) => /* @__PURE__ */ jsx4("option", { value: s, children: strings[`board.kpi.${s}`] ?? s }, s))
2127
2347
  ]
2128
2348
  }
2129
2349
  ),
@@ -2135,8 +2355,8 @@ function BoardFilters({
2135
2355
  value: filters.type?.[0] ?? "",
2136
2356
  onChange: (e) => setType(e.target.value),
2137
2357
  children: [
2138
- /* @__PURE__ */ jsx3("option", { value: "", children: strings["board.filter.type"] }),
2139
- TYPES.map((t) => /* @__PURE__ */ jsx3("option", { value: t, children: strings[`type.${t}`] ?? t }, t))
2358
+ /* @__PURE__ */ jsx4("option", { value: "", children: strings["board.filter.type"] }),
2359
+ TYPES.map((t) => /* @__PURE__ */ jsx4("option", { value: t, children: strings[`type.${t}`] ?? t }, t))
2140
2360
  ]
2141
2361
  }
2142
2362
  ),
@@ -2148,12 +2368,12 @@ function BoardFilters({
2148
2368
  value: filters.severity?.[0] ?? "",
2149
2369
  onChange: (e) => setSeverity(e.target.value),
2150
2370
  children: [
2151
- /* @__PURE__ */ jsx3("option", { value: "", children: strings["board.filter.severity"] }),
2152
- SEVERITIES.map((s) => /* @__PURE__ */ jsx3("option", { value: s, children: strings[`severity.${s}`] ?? s }, s))
2371
+ /* @__PURE__ */ jsx4("option", { value: "", children: strings["board.filter.severity"] }),
2372
+ SEVERITIES.map((s) => /* @__PURE__ */ jsx4("option", { value: s, children: strings[`severity.${s}`] ?? s }, s))
2153
2373
  ]
2154
2374
  }
2155
2375
  ),
2156
- /* @__PURE__ */ jsx3(
2376
+ /* @__PURE__ */ jsx4(
2157
2377
  "input",
2158
2378
  {
2159
2379
  type: "search",
@@ -2164,7 +2384,7 @@ function BoardFilters({
2164
2384
  }
2165
2385
  ),
2166
2386
  /* @__PURE__ */ jsxs4("label", { class: "board-filter-toggle", children: [
2167
- /* @__PURE__ */ jsx3(
2387
+ /* @__PURE__ */ jsx4(
2168
2388
  "input",
2169
2389
  {
2170
2390
  type: "checkbox",
@@ -2172,10 +2392,10 @@ function BoardFilters({
2172
2392
  onChange: toggleMine
2173
2393
  }
2174
2394
  ),
2175
- /* @__PURE__ */ jsx3("span", { children: strings["board.filter.mine"] })
2395
+ /* @__PURE__ */ jsx4("span", { children: strings["board.filter.mine"] })
2176
2396
  ] }),
2177
2397
  activeCount > 0 && /* @__PURE__ */ jsxs4("button", { type: "button", class: "board-filter-clear", onClick: clear, children: [
2178
- /* @__PURE__ */ jsx3(CloseIcon, {}),
2398
+ /* @__PURE__ */ jsx4(CloseIcon, {}),
2179
2399
  strings["board.filter.clear"]
2180
2400
  ] })
2181
2401
  ] });
@@ -2189,8 +2409,8 @@ function BoardList({
2189
2409
  thisPage
2190
2410
  }) {
2191
2411
  return /* @__PURE__ */ jsxs4(Fragment2, { children: [
2192
- /* @__PURE__ */ jsx3("div", { class: "board-list-count", children: strings[thisPage ? "board.list.count.page" : "board.list.count"].replace("{n}", String(rows.length)).replace("{total}", String(total)) }),
2193
- /* @__PURE__ */ jsx3("ul", { class: "board-list", role: "list", children: rows.map((row) => /* @__PURE__ */ jsx3(
2412
+ /* @__PURE__ */ jsx4("div", { class: "board-list-count", children: strings[thisPage ? "board.list.count.page" : "board.list.count"].replace("{n}", String(rows.length)).replace("{total}", String(total)) }),
2413
+ /* @__PURE__ */ jsx4("ul", { class: "board-list", role: "list", children: rows.map((row) => /* @__PURE__ */ jsx4(
2194
2414
  BoardRowCard,
2195
2415
  {
2196
2416
  row,
@@ -2209,7 +2429,7 @@ function BoardRowCard({
2209
2429
  strings
2210
2430
  }) {
2211
2431
  const author = row.is_mine ? strings["board.list.you"] : row.author_label || "\u2014";
2212
- return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs4(
2432
+ return /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4(
2213
2433
  "button",
2214
2434
  {
2215
2435
  type: "button",
@@ -2218,18 +2438,18 @@ function BoardRowCard({
2218
2438
  "aria-pressed": selected,
2219
2439
  children: [
2220
2440
  /* @__PURE__ */ jsxs4("div", { class: "board-row-badges", children: [
2221
- /* @__PURE__ */ jsx3("span", { class: `badge status-${row.status}`, children: strings[`status.${row.status}`] ?? row.status }),
2222
- /* @__PURE__ */ jsx3("span", { class: `badge severity-${row.severity}`, children: strings[`severity.${row.severity}`] ?? row.severity })
2441
+ /* @__PURE__ */ jsx4("span", { class: `badge status-${row.status}`, children: strings[`status.${row.status}`] ?? row.status }),
2442
+ /* @__PURE__ */ jsx4("span", { class: `badge severity-${row.severity}`, children: strings[`severity.${row.severity}`] ?? row.severity })
2223
2443
  ] }),
2224
- /* @__PURE__ */ jsx3("div", { class: "board-row-description", children: row.description }),
2444
+ /* @__PURE__ */ jsx4("div", { class: "board-row-description", children: row.description }),
2225
2445
  /* @__PURE__ */ jsxs4("div", { class: "board-row-meta", children: [
2226
- /* @__PURE__ */ jsx3("span", { class: "board-row-author", children: author }),
2446
+ /* @__PURE__ */ jsx4("span", { class: "board-row-author", children: author }),
2227
2447
  row.comment_count > 0 && /* @__PURE__ */ jsxs4("span", { class: "board-row-comments", children: [
2228
- /* @__PURE__ */ jsx3(CommentIcon, {}),
2448
+ /* @__PURE__ */ jsx4(CommentIcon, {}),
2229
2449
  " ",
2230
2450
  row.comment_count
2231
2451
  ] }),
2232
- /* @__PURE__ */ jsx3("span", { class: "board-row-time", children: formatRelative(row.created_at) })
2452
+ /* @__PURE__ */ jsx4("span", { class: "board-row-time", children: formatRelative(row.created_at) })
2233
2453
  ] })
2234
2454
  ]
2235
2455
  }
@@ -2241,50 +2461,77 @@ function BoardEmpty({
2241
2461
  onAllPages
2242
2462
  }) {
2243
2463
  return /* @__PURE__ */ jsxs4("div", { class: "board-empty", children: [
2244
- /* @__PURE__ */ jsx3(EmptyIllustration, {}),
2245
- /* @__PURE__ */ jsx3("h3", { children: strings[thisPage ? "board.list.empty.page.title" : "board.list.empty.title"] }),
2246
- /* @__PURE__ */ jsx3("p", { children: strings[thisPage ? "board.list.empty.page.description" : "board.list.empty.description"] }),
2247
- thisPage && /* @__PURE__ */ jsx3("button", { type: "button", class: "btn btn--ghost", onClick: onAllPages, children: strings["board.scope.allPages"] })
2464
+ /* @__PURE__ */ jsx4(EmptyIllustration, {}),
2465
+ /* @__PURE__ */ jsx4("h3", { children: strings[thisPage ? "board.list.empty.page.title" : "board.list.empty.title"] }),
2466
+ /* @__PURE__ */ jsx4("p", { children: strings[thisPage ? "board.list.empty.page.description" : "board.list.empty.description"] }),
2467
+ thisPage && /* @__PURE__ */ jsx4("button", { type: "button", class: "btn btn--ghost", onClick: onAllPages, children: strings["board.scope.allPages"] })
2248
2468
  ] });
2249
2469
  }
2250
2470
  function BoardListSkeleton() {
2251
- return /* @__PURE__ */ jsx3("ul", { class: "board-list board-list-skeleton", "aria-hidden": "true", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs4("div", { class: "board-row skeleton-row", children: [
2252
- /* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-badges" }),
2253
- /* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-text" }),
2254
- /* @__PURE__ */ jsx3("div", { class: "skeleton-line skeleton-text short" })
2471
+ return /* @__PURE__ */ jsx4("ul", { class: "board-list board-list-skeleton", "aria-hidden": "true", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4("div", { class: "board-row skeleton-row", children: [
2472
+ /* @__PURE__ */ jsx4("div", { class: "skeleton-line skeleton-badges" }),
2473
+ /* @__PURE__ */ jsx4("div", { class: "skeleton-line skeleton-text" }),
2474
+ /* @__PURE__ */ jsx4("div", { class: "skeleton-line skeleton-text short" })
2255
2475
  ] }) }, i)) });
2256
2476
  }
2257
2477
  function CommentIcon() {
2258
- return /* @__PURE__ */ jsx3("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
2478
+ return /* @__PURE__ */ jsx4("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
2259
2479
  }
2260
2480
  function CloseIcon() {
2261
2481
  return /* @__PURE__ */ jsxs4("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
2262
- /* @__PURE__ */ jsx3("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2263
- /* @__PURE__ */ jsx3("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2482
+ /* @__PURE__ */ jsx4("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2483
+ /* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2264
2484
  ] });
2265
2485
  }
2266
2486
  function EmptyIllustration() {
2267
2487
  return /* @__PURE__ */ jsxs4("svg", { width: "64", height: "64", viewBox: "0 0 64 64", fill: "none", "aria-hidden": "true", children: [
2268
- /* @__PURE__ */ jsx3("circle", { cx: "32", cy: "32", r: "28", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2" }),
2269
- /* @__PURE__ */ jsx3("path", { d: "M22 32h20M32 22v20", stroke: "currentColor", "stroke-opacity": "0.35", "stroke-width": "2", "stroke-linecap": "round" })
2488
+ /* @__PURE__ */ jsx4("circle", { cx: "32", cy: "32", r: "28", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2" }),
2489
+ /* @__PURE__ */ jsx4("path", { d: "M22 32h20M32 22v20", stroke: "currentColor", "stroke-opacity": "0.35", "stroke-width": "2", "stroke-linecap": "round" })
2270
2490
  ] });
2271
2491
  }
2272
2492
  function DetailEmptyIllustration() {
2273
2493
  return /* @__PURE__ */ jsxs4("svg", { width: "80", height: "80", viewBox: "0 0 64 64", fill: "none", "aria-hidden": "true", children: [
2274
- /* @__PURE__ */ jsx3("rect", { x: "10", y: "12", width: "44", height: "36", rx: "4", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2" }),
2275
- /* @__PURE__ */ jsx3("line", { x1: "18", y1: "22", x2: "46", y2: "22", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2", "stroke-linecap": "round" }),
2276
- /* @__PURE__ */ jsx3("line", { x1: "18", y1: "30", x2: "38", y2: "30", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2", "stroke-linecap": "round" }),
2277
- /* @__PURE__ */ jsx3("line", { x1: "18", y1: "38", x2: "32", y2: "38", stroke: "currentColor", "stroke-opacity": "0.14", "stroke-width": "2", "stroke-linecap": "round" })
2494
+ /* @__PURE__ */ jsx4("rect", { x: "10", y: "12", width: "44", height: "36", rx: "4", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2" }),
2495
+ /* @__PURE__ */ jsx4("line", { x1: "18", y1: "22", x2: "46", y2: "22", stroke: "currentColor", "stroke-opacity": "0.22", "stroke-width": "2", "stroke-linecap": "round" }),
2496
+ /* @__PURE__ */ jsx4("line", { x1: "18", y1: "30", x2: "38", y2: "30", stroke: "currentColor", "stroke-opacity": "0.18", "stroke-width": "2", "stroke-linecap": "round" }),
2497
+ /* @__PURE__ */ jsx4("line", { x1: "18", y1: "38", x2: "32", y2: "38", stroke: "currentColor", "stroke-opacity": "0.14", "stroke-width": "2", "stroke-linecap": "round" })
2278
2498
  ] });
2279
2499
  }
2280
2500
  function useDebouncedValue(value, ms) {
2281
- const [debounced, setDebounced] = useState2(value);
2501
+ const [debounced, setDebounced] = useState3(value);
2282
2502
  useEffect2(() => {
2283
2503
  const t = setTimeout(() => setDebounced(value), ms);
2284
2504
  return () => clearTimeout(t);
2285
2505
  }, [value, ms]);
2286
2506
  return debounced;
2287
2507
  }
2508
+ var SEVERITY_RANK = { blocker: 0, high: 1, medium: 2, low: 3 };
2509
+ function applyLocalFilters(rows, f) {
2510
+ let out = rows;
2511
+ if (f.status?.length) out = out.filter((r) => f.status.includes(r.status));
2512
+ if (f.type?.length) out = out.filter((r) => f.type.includes(r.feedback_type));
2513
+ if (f.severity?.length) out = out.filter((r) => f.severity.includes(r.severity));
2514
+ if (f.mine) out = out.filter((r) => r.is_mine);
2515
+ const sorted = out.slice();
2516
+ switch (f.ordering ?? "recent") {
2517
+ case "oldest":
2518
+ sorted.sort((a, b) => a.created_at.localeCompare(b.created_at));
2519
+ break;
2520
+ case "updated":
2521
+ sorted.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
2522
+ break;
2523
+ case "severity":
2524
+ sorted.sort(
2525
+ (a, b) => (SEVERITY_RANK[a.severity] ?? 9) - (SEVERITY_RANK[b.severity] ?? 9)
2526
+ );
2527
+ break;
2528
+ case "status":
2529
+ break;
2530
+ default:
2531
+ sorted.sort((a, b) => b.created_at.localeCompare(a.created_at));
2532
+ }
2533
+ return sorted;
2534
+ }
2288
2535
  function filtersHash(f) {
2289
2536
  return JSON.stringify({
2290
2537
  s: f.status?.slice().sort(),
@@ -2311,10 +2558,10 @@ function formatRelative(iso) {
2311
2558
  }
2312
2559
 
2313
2560
  // src/widget/ChangelogList.tsx
2314
- import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "preact/hooks";
2561
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState4 } from "preact/hooks";
2315
2562
 
2316
2563
  // src/widget/ReportRow.tsx
2317
- import { jsx as jsx4, jsxs as jsxs5 } from "preact/jsx-runtime";
2564
+ import { jsx as jsx5, jsxs as jsxs5 } from "preact/jsx-runtime";
2318
2565
  function statusClassName(status) {
2319
2566
  return `pill pill-status pill-status--${status}`;
2320
2567
  }
@@ -2340,27 +2587,31 @@ function repliesLabel(count, strings) {
2340
2587
  if (count === 1) return strings["mine.replies_one"];
2341
2588
  return strings["mine.replies_many"].replace("{count}", String(count));
2342
2589
  }
2343
- function ReportRow({ row, strings, onClick }) {
2590
+ function ReportRow({ row, strings, onClick, extraMeta }) {
2344
2591
  const preview = row.description.length > 120 ? row.description.slice(0, 117) + "\u2026" : row.description;
2345
2592
  return /* @__PURE__ */ jsxs5("button", { type: "button", class: "mine-row", onClick, children: [
2346
2593
  /* @__PURE__ */ jsxs5("div", { class: "mine-row-pills", children: [
2347
- /* @__PURE__ */ jsx4("span", { class: statusClassName(row.status), children: strings[`status.${row.status}`] ?? row.status }),
2348
- /* @__PURE__ */ jsx4("span", { class: typeClassName(), children: strings[`type.${row.feedback_type}`] }),
2349
- /* @__PURE__ */ jsx4("span", { class: severityClassName(row.severity), children: strings[`severity.${row.severity}`] })
2594
+ /* @__PURE__ */ jsx5("span", { class: statusClassName(row.status), children: strings[`status.${row.status}`] ?? row.status }),
2595
+ /* @__PURE__ */ jsx5("span", { class: typeClassName(), children: strings[`type.${row.feedback_type}`] }),
2596
+ /* @__PURE__ */ jsx5("span", { class: severityClassName(row.severity), children: strings[`severity.${row.severity}`] })
2350
2597
  ] }),
2351
- /* @__PURE__ */ jsx4("div", { class: "mine-row-preview", children: preview }),
2598
+ /* @__PURE__ */ jsx5("div", { class: "mine-row-preview", children: preview }),
2352
2599
  /* @__PURE__ */ jsxs5("div", { class: "mine-row-meta", children: [
2353
- /* @__PURE__ */ jsx4("span", { children: formatRelative2(row.updated_at || row.created_at) }),
2600
+ /* @__PURE__ */ jsx5("span", { children: formatRelative2(row.updated_at || row.created_at) }),
2354
2601
  row.comment_count > 0 && /* @__PURE__ */ jsxs5("span", { children: [
2355
2602
  "\xB7 ",
2356
2603
  repliesLabel(row.comment_count, strings)
2604
+ ] }),
2605
+ extraMeta && /* @__PURE__ */ jsxs5("span", { children: [
2606
+ "\xB7 ",
2607
+ extraMeta
2357
2608
  ] })
2358
2609
  ] })
2359
2610
  ] });
2360
2611
  }
2361
2612
 
2362
2613
  // src/widget/ChangelogList.tsx
2363
- import { jsx as jsx5, jsxs as jsxs6 } from "preact/jsx-runtime";
2614
+ import { jsx as jsx6, jsxs as jsxs6 } from "preact/jsx-runtime";
2364
2615
  var POLL_MS3 = 3e4;
2365
2616
  function isoWeekKey(iso) {
2366
2617
  const d = new Date(iso);
@@ -2396,9 +2647,9 @@ function formatWeekLabel(weekKey) {
2396
2647
  }
2397
2648
  }
2398
2649
  function ChangelogList({ api, externalId, strings, onSelect }) {
2399
- const [rows, setRows] = useState3(null);
2400
- const [error, setError] = useState3(null);
2401
- const [refreshing, setRefreshing] = useState3(false);
2650
+ const [rows, setRows] = useState4(null);
2651
+ const [error, setError] = useState4(null);
2652
+ const [refreshing, setRefreshing] = useState4(false);
2402
2653
  const mountedRef = useRef2(true);
2403
2654
  const fetchRows = async () => {
2404
2655
  setRefreshing(true);
@@ -2431,8 +2682,8 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
2431
2682
  const isEmpty = rows !== null && rows.length === 0;
2432
2683
  return /* @__PURE__ */ jsxs6("div", { class: "mine-list", children: [
2433
2684
  /* @__PURE__ */ jsxs6("div", { class: "mine-list-header", children: [
2434
- /* @__PURE__ */ jsx5("h2", { children: strings["tab.changelog"] }),
2435
- /* @__PURE__ */ jsx5(
2685
+ /* @__PURE__ */ jsx6("h2", { children: strings["tab.changelog"] }),
2686
+ /* @__PURE__ */ jsx6(
2436
2687
  "button",
2437
2688
  {
2438
2689
  type: "button",
@@ -2445,26 +2696,34 @@ function ChangelogList({ api, externalId, strings, onSelect }) {
2445
2696
  }
2446
2697
  )
2447
2698
  ] }),
2448
- isLoading && /* @__PURE__ */ jsx5("div", { class: "mine-loading", children: strings["mine.loading"] }),
2449
- error && /* @__PURE__ */ jsx5("div", { class: "error", children: error }),
2699
+ isLoading && /* @__PURE__ */ jsx6("div", { class: "mine-loading", children: strings["mine.loading"] }),
2700
+ error && /* @__PURE__ */ jsx6("div", { class: "error", children: error }),
2450
2701
  isEmpty && /* @__PURE__ */ jsxs6("div", { class: "mine-empty", children: [
2451
- /* @__PURE__ */ jsx5("strong", { children: strings["changelog.empty.title"] }),
2452
- /* @__PURE__ */ jsx5("p", { children: strings["changelog.empty.body"] })
2702
+ /* @__PURE__ */ jsx6("strong", { children: strings["changelog.empty.title"] }),
2703
+ /* @__PURE__ */ jsx6("p", { children: strings["changelog.empty.body"] })
2453
2704
  ] }),
2454
- groups.length > 0 && /* @__PURE__ */ jsx5("div", { class: "changelog-groups", children: groups.map((g) => /* @__PURE__ */ jsxs6("section", { class: "changelog-group", children: [
2705
+ groups.length > 0 && /* @__PURE__ */ jsx6("div", { class: "changelog-groups", children: groups.map((g) => /* @__PURE__ */ jsxs6("section", { class: "changelog-group", children: [
2455
2706
  /* @__PURE__ */ jsxs6("header", { class: "changelog-group-header", children: [
2456
- /* @__PURE__ */ jsx5("span", { class: "changelog-group-marker", "aria-hidden": "true", children: "\u25CF" }),
2457
- /* @__PURE__ */ jsx5("span", { class: "changelog-group-label", children: strings["changelog.week_of"].replace("{date}", g.label) }),
2458
- /* @__PURE__ */ jsx5("span", { class: "changelog-group-rule", "aria-hidden": "true" }),
2459
- /* @__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)) })
2707
+ /* @__PURE__ */ jsx6("span", { class: "changelog-group-marker", "aria-hidden": "true", children: "\u25CF" }),
2708
+ /* @__PURE__ */ jsx6("span", { class: "changelog-group-label", children: strings["changelog.week_of"].replace("{date}", g.label) }),
2709
+ /* @__PURE__ */ jsx6("span", { class: "changelog-group-rule", "aria-hidden": "true" }),
2710
+ /* @__PURE__ */ jsx6("span", { class: "changelog-group-count", children: strings[g.rows.length === 1 ? "changelog.resolved_one" : "changelog.resolved_many"].replace("{count}", String(g.rows.length)) })
2460
2711
  ] }),
2461
- /* @__PURE__ */ jsx5("ul", { class: "mine-rows", children: g.rows.map((row) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsx5(ReportRow, { row, strings, onClick: () => onSelect(row) }) })) })
2712
+ /* @__PURE__ */ jsx6("ul", { class: "mine-rows", children: g.rows.map((row) => /* @__PURE__ */ jsx6("li", { children: /* @__PURE__ */ jsx6(
2713
+ ReportRow,
2714
+ {
2715
+ row,
2716
+ strings,
2717
+ onClick: () => onSelect(row),
2718
+ extraMeta: row.pr_number ? `PR #${row.pr_number}` : void 0
2719
+ }
2720
+ ) })) })
2462
2721
  ] }, g.weekKey)) })
2463
2722
  ] });
2464
2723
  }
2465
2724
 
2466
2725
  // src/widget/Fab.tsx
2467
- import { jsx as jsx6, jsxs as jsxs7 } from "preact/jsx-runtime";
2726
+ import { jsx as jsx7, jsxs as jsxs7 } from "preact/jsx-runtime";
2468
2727
  function BugIcon() {
2469
2728
  return /* @__PURE__ */ jsxs7(
2470
2729
  "svg",
@@ -2480,34 +2739,34 @@ function BugIcon() {
2480
2739
  "aria-hidden": "true",
2481
2740
  focusable: "false",
2482
2741
  children: [
2483
- /* @__PURE__ */ jsx6("path", { d: "m8 2 1.88 1.88" }),
2484
- /* @__PURE__ */ jsx6("path", { d: "M14.12 3.88 16 2" }),
2485
- /* @__PURE__ */ jsx6("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
2486
- /* @__PURE__ */ jsx6("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
2487
- /* @__PURE__ */ jsx6("path", { d: "M12 20v-9" }),
2488
- /* @__PURE__ */ jsx6("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
2489
- /* @__PURE__ */ jsx6("path", { d: "M6 13H2" }),
2490
- /* @__PURE__ */ jsx6("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
2491
- /* @__PURE__ */ jsx6("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
2492
- /* @__PURE__ */ jsx6("path", { d: "M22 13h-4" }),
2493
- /* @__PURE__ */ jsx6("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
2742
+ /* @__PURE__ */ jsx7("path", { d: "m8 2 1.88 1.88" }),
2743
+ /* @__PURE__ */ jsx7("path", { d: "M14.12 3.88 16 2" }),
2744
+ /* @__PURE__ */ jsx7("path", { d: "M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" }),
2745
+ /* @__PURE__ */ jsx7("path", { d: "M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6" }),
2746
+ /* @__PURE__ */ jsx7("path", { d: "M12 20v-9" }),
2747
+ /* @__PURE__ */ jsx7("path", { d: "M6.53 9C4.6 8.8 3 7.1 3 5" }),
2748
+ /* @__PURE__ */ jsx7("path", { d: "M6 13H2" }),
2749
+ /* @__PURE__ */ jsx7("path", { d: "M3 21c0-2.1 1.7-3.9 3.8-4" }),
2750
+ /* @__PURE__ */ jsx7("path", { d: "M20.97 5c0 2.1-1.6 3.8-3.5 4" }),
2751
+ /* @__PURE__ */ jsx7("path", { d: "M22 13h-4" }),
2752
+ /* @__PURE__ */ jsx7("path", { d: "M17.2 17c2.1.1 3.8 1.9 3.8 4" })
2494
2753
  ]
2495
2754
  }
2496
2755
  );
2497
2756
  }
2498
2757
  function Fab({ label, onClick, count }) {
2499
2758
  return /* @__PURE__ */ jsxs7("button", { type: "button", class: "fab", "aria-label": label, title: label, onClick, children: [
2500
- /* @__PURE__ */ jsx6(BugIcon, {}),
2501
- count !== void 0 && count > 0 && /* @__PURE__ */ jsx6("span", { class: "fab-badge", "aria-hidden": "true", children: count > 9 ? "9+" : String(count) })
2759
+ /* @__PURE__ */ jsx7(BugIcon, {}),
2760
+ count !== void 0 && count > 0 && /* @__PURE__ */ jsx7("span", { class: "fab-badge", "aria-hidden": "true", children: count > 9 ? "9+" : String(count) })
2502
2761
  ] });
2503
2762
  }
2504
2763
 
2505
2764
  // src/widget/Form.tsx
2506
- import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "preact/hooks";
2765
+ import { useEffect as useEffect5, useRef as useRef4, useState as useState6 } from "preact/hooks";
2507
2766
 
2508
2767
  // src/widget/Annotator.tsx
2509
- import { useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState4 } from "preact/hooks";
2510
- import { jsx as jsx7, jsxs as jsxs8 } from "preact/jsx-runtime";
2768
+ import { useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState5 } from "preact/hooks";
2769
+ import { jsx as jsx8, jsxs as jsxs8 } from "preact/jsx-runtime";
2511
2770
  var COLORS = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#ffffff"];
2512
2771
  var HIGHLIGHT_ALPHA = 0.35;
2513
2772
  function drawShape(ctx, shape, sourceImage) {
@@ -2629,43 +2888,43 @@ function drawBlur(ctx, shape, sourceImage) {
2629
2888
  ctx.imageSmoothingEnabled = true;
2630
2889
  }
2631
2890
  var Icon = {
2632
- rect: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("rect", { x: "2", y: "3", width: "12", height: "10", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }) }),
2633
- arrow: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M2 8h11M9 4l4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2634
- pencil: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round" }) }),
2635
- text: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M3 3h10M8 3v10M5 13h6", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) }),
2891
+ rect: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("rect", { x: "2", y: "3", width: "12", height: "10", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }) }),
2892
+ arrow: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M2 8h11M9 4l4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2893
+ pencil: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linejoin": "round" }) }),
2894
+ text: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M3 3h10M8 3v10M5 13h6", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) }),
2636
2895
  highlight: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
2637
- /* @__PURE__ */ jsx7("path", { d: "M3 13l3-3L11 5l-2-2-5 5-3 3v2h2zM10 4l2 2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }),
2638
- /* @__PURE__ */ jsx7("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
2896
+ /* @__PURE__ */ jsx8("path", { d: "M3 13l3-3L11 5l-2-2-5 5-3 3v2h2zM10 4l2 2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }),
2897
+ /* @__PURE__ */ jsx8("path", { d: "M2 14h12", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" })
2639
2898
  ] }),
2640
2899
  blur: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
2641
- /* @__PURE__ */ jsx7("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
2642
- /* @__PURE__ */ jsx7("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
2643
- /* @__PURE__ */ jsx7("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
2644
- /* @__PURE__ */ jsx7("rect", { x: "2", y: "7", width: "3", height: "3", fill: "currentColor", opacity: "0.6" }),
2645
- /* @__PURE__ */ jsx7("rect", { x: "6", y: "7", width: "3", height: "3", fill: "currentColor", opacity: "0.85" }),
2646
- /* @__PURE__ */ jsx7("rect", { x: "10", y: "7", width: "4", height: "3", fill: "currentColor", opacity: "0.5" }),
2647
- /* @__PURE__ */ jsx7("rect", { x: "2", y: "11", width: "4", height: "3", fill: "currentColor", opacity: "0.4" }),
2648
- /* @__PURE__ */ jsx7("rect", { x: "7", y: "11", width: "3", height: "3", fill: "currentColor", opacity: "0.65" }),
2649
- /* @__PURE__ */ jsx7("rect", { x: "11", y: "11", width: "3", height: "3", fill: "currentColor", opacity: "0.85" })
2900
+ /* @__PURE__ */ jsx8("rect", { x: "2", y: "2", width: "4", height: "4", fill: "currentColor", opacity: "0.85" }),
2901
+ /* @__PURE__ */ jsx8("rect", { x: "7", y: "2", width: "3", height: "3", fill: "currentColor", opacity: "0.55" }),
2902
+ /* @__PURE__ */ jsx8("rect", { x: "11", y: "2", width: "3", height: "4", fill: "currentColor", opacity: "0.4" }),
2903
+ /* @__PURE__ */ jsx8("rect", { x: "2", y: "7", width: "3", height: "3", fill: "currentColor", opacity: "0.6" }),
2904
+ /* @__PURE__ */ jsx8("rect", { x: "6", y: "7", width: "3", height: "3", fill: "currentColor", opacity: "0.85" }),
2905
+ /* @__PURE__ */ jsx8("rect", { x: "10", y: "7", width: "4", height: "3", fill: "currentColor", opacity: "0.5" }),
2906
+ /* @__PURE__ */ jsx8("rect", { x: "2", y: "11", width: "4", height: "3", fill: "currentColor", opacity: "0.4" }),
2907
+ /* @__PURE__ */ jsx8("rect", { x: "7", y: "11", width: "3", height: "3", fill: "currentColor", opacity: "0.65" }),
2908
+ /* @__PURE__ */ jsx8("rect", { x: "11", y: "11", width: "3", height: "3", fill: "currentColor", opacity: "0.85" })
2650
2909
  ] }),
2651
- undo: /* @__PURE__ */ jsx7("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M4 7l3-3M4 7l3 3M4 7h6a3 3 0 0 1 0 6H7", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2652
- redo: /* @__PURE__ */ jsx7("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M12 7l-3-3M12 7l-3 3M12 7H6a3 3 0 0 0 0 6h2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2653
- trash: /* @__PURE__ */ jsx7("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M3 4h10M6 4V2.5h4V4M5 4l.5 9h5L11 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2654
- close: /* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M4 4l8 8M12 4l-8 8", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) })
2910
+ undo: /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M4 7l3-3M4 7l3 3M4 7h6a3 3 0 0 1 0 6H7", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2911
+ redo: /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M12 7l-3-3M12 7l-3 3M12 7H6a3 3 0 0 0 0 6h2", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2912
+ trash: /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M3 4h10M6 4V2.5h4V4M5 4l.5 9h5L11 4", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round" }) }),
2913
+ close: /* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M4 4l8 8M12 4l-8 8", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round" }) })
2655
2914
  };
2656
2915
  function Annotator({ imageBlob, strings, onSave, onCancel }) {
2657
2916
  const canvasRef = useRef3(null);
2658
2917
  const containerRef = useRef3(null);
2659
2918
  const imageRef = useRef3(null);
2660
- const [tool, setTool] = useState4("rectangle");
2661
- const [color, setColor] = useState4(COLORS[0]);
2662
- const [shapes, setShapes] = useState4([]);
2663
- const [past, setPast] = useState4([]);
2664
- const [future, setFuture] = useState4([]);
2919
+ const [tool, setTool] = useState5("rectangle");
2920
+ const [color, setColor] = useState5(COLORS[0]);
2921
+ const [shapes, setShapes] = useState5([]);
2922
+ const [past, setPast] = useState5([]);
2923
+ const [future, setFuture] = useState5([]);
2665
2924
  const isDrawingRef = useRef3(false);
2666
- const [draftShape, setDraftShape] = useState4(null);
2667
- const [imageLoaded, setImageLoaded] = useState4(false);
2668
- const [saving, setSaving] = useState4(false);
2925
+ const [draftShape, setDraftShape] = useState5(null);
2926
+ const [imageLoaded, setImageLoaded] = useState5(false);
2927
+ const [saving, setSaving] = useState5(false);
2669
2928
  const scaleRef = useRef3(1);
2670
2929
  function addShape(shape) {
2671
2930
  setShapes((s) => {
@@ -2885,7 +3144,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2885
3144
  { id: "highlight", icon: Icon.highlight, label: strings["annotator.tool.highlight"] },
2886
3145
  { id: "blur", icon: Icon.blur, label: strings["annotator.tool.blur"] }
2887
3146
  ];
2888
- return /* @__PURE__ */ jsx7(
3147
+ return /* @__PURE__ */ jsx8(
2889
3148
  "div",
2890
3149
  {
2891
3150
  class: "annotator-backdrop",
@@ -2895,8 +3154,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2895
3154
  },
2896
3155
  children: /* @__PURE__ */ jsxs8("div", { class: "annotator", role: "dialog", "aria-modal": "true", "aria-label": strings["annotator.title"], children: [
2897
3156
  /* @__PURE__ */ jsxs8("div", { class: "annotator-header", children: [
2898
- /* @__PURE__ */ jsx7("span", { children: strings["annotator.title"] }),
2899
- /* @__PURE__ */ jsx7(
3157
+ /* @__PURE__ */ jsx8("span", { children: strings["annotator.title"] }),
3158
+ /* @__PURE__ */ jsx8(
2900
3159
  "button",
2901
3160
  {
2902
3161
  type: "button",
@@ -2908,7 +3167,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2908
3167
  )
2909
3168
  ] }),
2910
3169
  /* @__PURE__ */ jsxs8("div", { class: "annotator-toolbar", children: [
2911
- /* @__PURE__ */ jsx7("div", { class: "annotator-tools", children: tools.map((t) => /* @__PURE__ */ jsx7(
3170
+ /* @__PURE__ */ jsx8("div", { class: "annotator-tools", children: tools.map((t) => /* @__PURE__ */ jsx8(
2912
3171
  "button",
2913
3172
  {
2914
3173
  type: "button",
@@ -2921,9 +3180,9 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2921
3180
  },
2922
3181
  t.id
2923
3182
  )) }),
2924
- /* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
3183
+ /* @__PURE__ */ jsx8("span", { class: "annotator-sep" }),
2925
3184
  /* @__PURE__ */ jsxs8("div", { class: "annotator-colors", children: [
2926
- COLORS.map((c) => /* @__PURE__ */ jsx7(
3185
+ COLORS.map((c) => /* @__PURE__ */ jsx8(
2927
3186
  "button",
2928
3187
  {
2929
3188
  type: "button",
@@ -2935,7 +3194,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2935
3194
  },
2936
3195
  c
2937
3196
  )),
2938
- /* @__PURE__ */ jsx7("label", { class: "annotator-color annotator-color-picker", title: strings["annotator.color_picker"], children: /* @__PURE__ */ jsx7(
3197
+ /* @__PURE__ */ jsx8("label", { class: "annotator-color annotator-color-picker", title: strings["annotator.color_picker"], children: /* @__PURE__ */ jsx8(
2939
3198
  "input",
2940
3199
  {
2941
3200
  type: "color",
@@ -2945,7 +3204,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2945
3204
  }
2946
3205
  ) })
2947
3206
  ] }),
2948
- /* @__PURE__ */ jsx7("span", { class: "annotator-sep" }),
3207
+ /* @__PURE__ */ jsx8("span", { class: "annotator-sep" }),
2949
3208
  /* @__PURE__ */ jsxs8(
2950
3209
  "button",
2951
3210
  {
@@ -2956,7 +3215,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2956
3215
  title: strings["annotator.undo"],
2957
3216
  children: [
2958
3217
  Icon.undo,
2959
- /* @__PURE__ */ jsx7("span", { children: strings["annotator.undo"] })
3218
+ /* @__PURE__ */ jsx8("span", { children: strings["annotator.undo"] })
2960
3219
  ]
2961
3220
  }
2962
3221
  ),
@@ -2970,7 +3229,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2970
3229
  title: strings["annotator.redo"],
2971
3230
  children: [
2972
3231
  Icon.redo,
2973
- /* @__PURE__ */ jsx7("span", { children: strings["annotator.redo"] })
3232
+ /* @__PURE__ */ jsx8("span", { children: strings["annotator.redo"] })
2974
3233
  ]
2975
3234
  }
2976
3235
  ),
@@ -2983,18 +3242,18 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
2983
3242
  disabled: shapes.length === 0,
2984
3243
  children: [
2985
3244
  Icon.trash,
2986
- /* @__PURE__ */ jsx7("span", { children: strings["annotator.clear"] })
3245
+ /* @__PURE__ */ jsx8("span", { children: strings["annotator.clear"] })
2987
3246
  ]
2988
3247
  }
2989
3248
  ),
2990
- /* @__PURE__ */ jsx7("span", { class: "annotator-spacer" }),
3249
+ /* @__PURE__ */ jsx8("span", { class: "annotator-spacer" }),
2991
3250
  /* @__PURE__ */ jsxs8("span", { class: "annotator-count", children: [
2992
3251
  shapes.length,
2993
3252
  " ",
2994
3253
  strings["annotator.count_suffix"]
2995
3254
  ] })
2996
3255
  ] }),
2997
- /* @__PURE__ */ jsx7("div", { ref: containerRef, class: "annotator-canvas-wrap", children: !imageLoaded ? /* @__PURE__ */ jsx7("span", { class: "annotator-loading", children: strings["annotator.loading"] }) : /* @__PURE__ */ jsx7(
3256
+ /* @__PURE__ */ jsx8("div", { ref: containerRef, class: "annotator-canvas-wrap", children: !imageLoaded ? /* @__PURE__ */ jsx8("span", { class: "annotator-loading", children: strings["annotator.loading"] }) : /* @__PURE__ */ jsx8(
2998
3257
  "canvas",
2999
3258
  {
3000
3259
  ref: canvasRef,
@@ -3006,8 +3265,8 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
3006
3265
  }
3007
3266
  ) }),
3008
3267
  /* @__PURE__ */ jsxs8("div", { class: "annotator-footer", children: [
3009
- /* @__PURE__ */ jsx7("button", { type: "button", class: "btn", onClick: onCancel, children: strings["form.cancel"] }),
3010
- /* @__PURE__ */ jsx7(
3268
+ /* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, children: strings["form.cancel"] }),
3269
+ /* @__PURE__ */ jsx8(
3011
3270
  "button",
3012
3271
  {
3013
3272
  type: "button",
@@ -3024,7 +3283,7 @@ function Annotator({ imageBlob, strings, onSave, onCancel }) {
3024
3283
  }
3025
3284
 
3026
3285
  // src/widget/Form.tsx
3027
- import { jsx as jsx8, jsxs as jsxs9 } from "preact/jsx-runtime";
3286
+ import { jsx as jsx9, jsxs as jsxs9 } from "preact/jsx-runtime";
3028
3287
  var TYPES2 = ["bug", "feature", "question", "praise", "typo"];
3029
3288
  var SEVERITIES2 = ["blocker", "high", "medium", "low"];
3030
3289
  function Form({
@@ -3036,13 +3295,13 @@ function Form({
3036
3295
  submittedSeq,
3037
3296
  onDirtyChange
3038
3297
  }) {
3039
- const [description, setDescription] = useState5("");
3040
- const [feedbackType, setFeedbackType] = useState5("bug");
3041
- const [severity, setSeverity] = useState5("medium");
3042
- const [localError, setLocalError] = useState5("");
3043
- const [shots, setShots] = useState5([]);
3044
- const [isDragOver, setIsDragOver] = useState5(false);
3045
- const [annotatingIndex, setAnnotatingIndex] = useState5(null);
3298
+ const [description, setDescription] = useState6("");
3299
+ const [feedbackType, setFeedbackType] = useState6("bug");
3300
+ const [severity, setSeverity] = useState6("medium");
3301
+ const [localError, setLocalError] = useState6("");
3302
+ const [shots, setShots] = useState6([]);
3303
+ const [isDragOver, setIsDragOver] = useState6(false);
3304
+ const [annotatingIndex, setAnnotatingIndex] = useState6(null);
3046
3305
  const fileInputRef = useRef4(null);
3047
3306
  const dropZoneRef = useRef4(null);
3048
3307
  const submitting = status === "submitting";
@@ -3164,10 +3423,10 @@ function Form({
3164
3423
  onSubmit(values);
3165
3424
  };
3166
3425
  return /* @__PURE__ */ jsxs9("form", { onSubmit: handleSubmit, children: [
3167
- /* @__PURE__ */ jsx8("h2", { children: strings["form.title"] }),
3426
+ /* @__PURE__ */ jsx9("h2", { children: strings["form.title"] }),
3168
3427
  /* @__PURE__ */ jsxs9("div", { class: "field", children: [
3169
- /* @__PURE__ */ jsx8("label", { for: "mfb-desc", children: strings["form.description.label"] }),
3170
- /* @__PURE__ */ jsx8(
3428
+ /* @__PURE__ */ jsx9("label", { for: "mfb-desc", children: strings["form.description.label"] }),
3429
+ /* @__PURE__ */ jsx9(
3171
3430
  "textarea",
3172
3431
  {
3173
3432
  id: "mfb-desc",
@@ -3179,33 +3438,33 @@ function Form({
3179
3438
  ] }),
3180
3439
  /* @__PURE__ */ jsxs9("div", { class: "row", children: [
3181
3440
  /* @__PURE__ */ jsxs9("div", { class: "field", children: [
3182
- /* @__PURE__ */ jsx8("label", { for: "mfb-type", children: strings["form.type.label"] }),
3183
- /* @__PURE__ */ jsx8(
3441
+ /* @__PURE__ */ jsx9("label", { for: "mfb-type", children: strings["form.type.label"] }),
3442
+ /* @__PURE__ */ jsx9(
3184
3443
  "select",
3185
3444
  {
3186
3445
  id: "mfb-type",
3187
3446
  value: feedbackType,
3188
3447
  onChange: (e) => setFeedbackType(e.target.value),
3189
- children: TYPES2.map((t) => /* @__PURE__ */ jsx8("option", { value: t, children: strings[`type.${t}`] }))
3448
+ children: TYPES2.map((t) => /* @__PURE__ */ jsx9("option", { value: t, children: strings[`type.${t}`] }))
3190
3449
  }
3191
3450
  )
3192
3451
  ] }),
3193
3452
  /* @__PURE__ */ jsxs9("div", { class: "field", children: [
3194
- /* @__PURE__ */ jsx8("label", { for: "mfb-sev", children: strings["form.severity.label"] }),
3195
- /* @__PURE__ */ jsx8(
3453
+ /* @__PURE__ */ jsx9("label", { for: "mfb-sev", children: strings["form.severity.label"] }),
3454
+ /* @__PURE__ */ jsx9(
3196
3455
  "select",
3197
3456
  {
3198
3457
  id: "mfb-sev",
3199
3458
  value: severity,
3200
3459
  onChange: (e) => setSeverity(e.target.value),
3201
- children: SEVERITIES2.map((s) => /* @__PURE__ */ jsx8("option", { value: s, children: strings[`severity.${s}`] }))
3460
+ children: SEVERITIES2.map((s) => /* @__PURE__ */ jsx9("option", { value: s, children: strings[`severity.${s}`] }))
3202
3461
  }
3203
3462
  )
3204
3463
  ] })
3205
3464
  ] }),
3206
3465
  /* @__PURE__ */ jsxs9("div", { class: "field", children: [
3207
- /* @__PURE__ */ jsx8("label", { children: strings["form.screenshot.label"] }),
3208
- /* @__PURE__ */ jsx8(
3466
+ /* @__PURE__ */ jsx9("label", { children: strings["form.screenshot.label"] }),
3467
+ /* @__PURE__ */ jsx9(
3209
3468
  "input",
3210
3469
  {
3211
3470
  ref: fileInputRef,
@@ -3218,8 +3477,8 @@ function Form({
3218
3477
  tabIndex: -1
3219
3478
  }
3220
3479
  ),
3221
- shots.length > 0 && /* @__PURE__ */ jsx8("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */ jsxs9("div", { class: "screenshot-preview", children: [
3222
- /* @__PURE__ */ jsx8("img", { src: shot.preview, alt: "" }),
3480
+ shots.length > 0 && /* @__PURE__ */ jsx9("div", { class: "screenshot-strip", children: shots.map((shot, index) => /* @__PURE__ */ jsxs9("div", { class: "screenshot-preview", children: [
3481
+ /* @__PURE__ */ jsx9("img", { src: shot.preview, alt: "" }),
3223
3482
  /* @__PURE__ */ jsxs9("div", { class: "screenshot-preview-actions", children: [
3224
3483
  /* @__PURE__ */ jsxs9(
3225
3484
  "button",
@@ -3229,15 +3488,15 @@ function Form({
3229
3488
  onClick: () => setAnnotatingIndex(index),
3230
3489
  children: [
3231
3490
  /* @__PURE__ */ jsxs9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
3232
- /* @__PURE__ */ jsx8("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
3233
- /* @__PURE__ */ jsx8("path", { d: "M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
3491
+ /* @__PURE__ */ jsx9("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
3492
+ /* @__PURE__ */ jsx9("path", { d: "M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" })
3234
3493
  ] }),
3235
3494
  strings["form.screenshot.annotate"]
3236
3495
  ]
3237
3496
  }
3238
3497
  ),
3239
3498
  /* @__PURE__ */ jsxs9("button", { type: "button", class: "btn", onClick: () => removeShot(index), children: [
3240
- /* @__PURE__ */ jsx8("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) }),
3499
+ /* @__PURE__ */ jsx9("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx9("path", { d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z" }) }),
3241
3500
  strings["form.screenshot.remove"]
3242
3501
  ] })
3243
3502
  ] })
@@ -3262,33 +3521,33 @@ function Form({
3262
3521
  onDrop: handleDrop,
3263
3522
  children: [
3264
3523
  /* @__PURE__ */ jsxs9("svg", { class: "screenshot-icon", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.6", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: [
3265
- /* @__PURE__ */ jsx8("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
3266
- /* @__PURE__ */ jsx8("polyline", { points: "17 8 12 3 7 8" }),
3267
- /* @__PURE__ */ jsx8("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
3524
+ /* @__PURE__ */ jsx9("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
3525
+ /* @__PURE__ */ jsx9("polyline", { points: "17 8 12 3 7 8" }),
3526
+ /* @__PURE__ */ jsx9("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
3268
3527
  ] }),
3269
3528
  /* @__PURE__ */ jsxs9("div", { class: "screenshot-cta", children: [
3270
- /* @__PURE__ */ jsx8("strong", { children: strings["form.screenshot.cta_click"] }),
3529
+ /* @__PURE__ */ jsx9("strong", { children: strings["form.screenshot.cta_click"] }),
3271
3530
  ", ",
3272
3531
  strings["form.screenshot.cta_rest"]
3273
3532
  ] }),
3274
- /* @__PURE__ */ jsx8("div", { class: "screenshot-formats", children: strings["form.screenshot.formats"] })
3533
+ /* @__PURE__ */ jsx9("div", { class: "screenshot-formats", children: strings["form.screenshot.formats"] })
3275
3534
  ]
3276
3535
  }
3277
3536
  )
3278
3537
  ] }),
3279
3538
  pageUrl && /* @__PURE__ */ jsxs9("div", { class: "page-context", title: pageUrl, children: [
3280
- /* @__PURE__ */ jsx8("span", { class: "page-context-label", children: strings["form.context.label"] }),
3281
- /* @__PURE__ */ jsx8("span", { class: "page-context-url", children: truncateUrl(pageUrl, 90) })
3539
+ /* @__PURE__ */ jsx9("span", { class: "page-context-label", children: strings["form.context.label"] }),
3540
+ /* @__PURE__ */ jsx9("span", { class: "page-context-url", children: truncateUrl(pageUrl, 90) })
3282
3541
  ] }),
3283
- /* @__PURE__ */ jsx8("div", { class: "capture-notice", children: strings["form.capture.notice"] }),
3284
- localError && /* @__PURE__ */ jsx8("div", { class: "error", children: localError }),
3285
- status === "error" && errorMessage && /* @__PURE__ */ jsx8("div", { class: "error", children: errorMessage }),
3286
- status === "success" && /* @__PURE__ */ jsx8("div", { class: "success", children: submittedSeq !== void 0 ? strings["form.success.seq"].replace("{seq}", String(submittedSeq)) : strings["form.success"] }),
3542
+ /* @__PURE__ */ jsx9("div", { class: "capture-notice", children: strings["form.capture.notice"] }),
3543
+ localError && /* @__PURE__ */ jsx9("div", { class: "error", children: localError }),
3544
+ status === "error" && errorMessage && /* @__PURE__ */ jsx9("div", { class: "error", children: errorMessage }),
3545
+ status === "success" && /* @__PURE__ */ jsx9("div", { class: "success", children: submittedSeq !== void 0 ? strings["form.success.seq"].replace("{seq}", String(submittedSeq)) : strings["form.success"] }),
3287
3546
  /* @__PURE__ */ jsxs9("div", { class: "actions", children: [
3288
- /* @__PURE__ */ jsx8("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
3289
- /* @__PURE__ */ jsx8("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
3547
+ /* @__PURE__ */ jsx9("button", { type: "button", class: "btn", onClick: onCancel, disabled: submitting, children: strings["form.cancel"] }),
3548
+ /* @__PURE__ */ jsx9("button", { type: "submit", class: "btn btn--primary", disabled: submitting, children: submitLabel })
3290
3549
  ] }),
3291
- annotatingIndex !== null && shots[annotatingIndex] && /* @__PURE__ */ jsx8(
3550
+ annotatingIndex !== null && shots[annotatingIndex] && /* @__PURE__ */ jsx9(
3292
3551
  Annotator,
3293
3552
  {
3294
3553
  imageBlob: shots[annotatingIndex].blob,
@@ -3301,10 +3560,10 @@ function Form({
3301
3560
  }
3302
3561
 
3303
3562
  // src/widget/MineList.tsx
3304
- import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "preact/hooks";
3563
+ import { useEffect as useEffect6, useRef as useRef5, useState as useState7 } from "preact/hooks";
3305
3564
 
3306
3565
  // src/widget/KpiStrip.tsx
3307
- import { jsx as jsx9, jsxs as jsxs10 } from "preact/jsx-runtime";
3566
+ import { jsx as jsx10, jsxs as jsxs10 } from "preact/jsx-runtime";
3308
3567
  function computeKpiCounts(rows) {
3309
3568
  const counts = {
3310
3569
  new: 0,
@@ -3371,7 +3630,7 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
3371
3630
  value: resolutionRate === null ? "\u2014" : `${resolutionRate}%`
3372
3631
  }
3373
3632
  ];
3374
- return /* @__PURE__ */ jsx9("div", { class: "kpi-strip", role: "toolbar", children: cells.map((c) => {
3633
+ return /* @__PURE__ */ jsx10("div", { class: "kpi-strip", role: "toolbar", children: cells.map((c) => {
3375
3634
  const active = filter === c.key;
3376
3635
  const toggleTo = active ? "all" : c.key;
3377
3636
  return /* @__PURE__ */ jsxs10(
@@ -3382,8 +3641,8 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
3382
3641
  onClick: () => onFilter(toggleTo),
3383
3642
  "aria-pressed": active,
3384
3643
  children: [
3385
- /* @__PURE__ */ jsx9("span", { class: "kpi-value", children: c.value }),
3386
- /* @__PURE__ */ jsx9("span", { class: "kpi-label", children: c.label })
3644
+ /* @__PURE__ */ jsx10("span", { class: "kpi-value", children: c.value }),
3645
+ /* @__PURE__ */ jsx10("span", { class: "kpi-label", children: c.label })
3387
3646
  ]
3388
3647
  }
3389
3648
  );
@@ -3391,16 +3650,16 @@ function KpiStrip({ rows, filter, onFilter, strings }) {
3391
3650
  }
3392
3651
 
3393
3652
  // src/widget/MineList.tsx
3394
- import { jsx as jsx10, jsxs as jsxs11 } from "preact/jsx-runtime";
3653
+ import { jsx as jsx11, jsxs as jsxs11 } from "preact/jsx-runtime";
3395
3654
  var POLL_MS4 = 3e4;
3396
3655
  function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
3397
- const [rows, setRows] = useState6(null);
3398
- const [error, setError] = useState6(null);
3399
- const [refreshing, setRefreshing] = useState6(false);
3400
- const [filter, setFilter] = useState6("all");
3401
- const [jumpValue, setJumpValue] = useState6("");
3402
- const [jumpError, setJumpError] = useState6(false);
3403
- const [jumping, setJumping] = useState6(false);
3656
+ const [rows, setRows] = useState7(null);
3657
+ const [error, setError] = useState7(null);
3658
+ const [refreshing, setRefreshing] = useState7(false);
3659
+ const [filter, setFilter] = useState7("all");
3660
+ const [jumpValue, setJumpValue] = useState7("");
3661
+ const [jumpError, setJumpError] = useState7(false);
3662
+ const [jumping, setJumping] = useState7(false);
3404
3663
  const mountedRef = useRef5(true);
3405
3664
  const handleJump = async (e) => {
3406
3665
  e.preventDefault();
@@ -3452,10 +3711,10 @@ function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
3452
3711
  const visibleEmpty = !!rows && rows.length > 0 && (visibleRows?.length ?? 0) === 0;
3453
3712
  return /* @__PURE__ */ jsxs11("div", { class: "mine-list", children: [
3454
3713
  /* @__PURE__ */ jsxs11("div", { class: "mine-list-header", children: [
3455
- /* @__PURE__ */ jsx10("h2", { children: strings["tab.mine"] }),
3714
+ /* @__PURE__ */ jsx11("h2", { children: strings["tab.mine"] }),
3456
3715
  /* @__PURE__ */ jsxs11("div", { class: "mine-list-header-actions", children: [
3457
3716
  onOpenSeq && /* @__PURE__ */ jsxs11("form", { class: "mine-jump", onSubmit: handleJump, children: [
3458
- /* @__PURE__ */ jsx10(
3717
+ /* @__PURE__ */ jsx11(
3459
3718
  "input",
3460
3719
  {
3461
3720
  type: "text",
@@ -3470,9 +3729,9 @@ function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
3470
3729
  disabled: jumping
3471
3730
  }
3472
3731
  ),
3473
- /* @__PURE__ */ jsx10("button", { type: "submit", class: "btn", disabled: jumping || !jumpValue.trim(), children: strings["mine.jump.go"] })
3732
+ /* @__PURE__ */ jsx11("button", { type: "submit", class: "btn", disabled: jumping || !jumpValue.trim(), children: strings["mine.jump.go"] })
3474
3733
  ] }),
3475
- /* @__PURE__ */ jsx10(
3734
+ /* @__PURE__ */ jsx11(
3476
3735
  "button",
3477
3736
  {
3478
3737
  type: "button",
@@ -3486,22 +3745,25 @@ function MineList({ api, externalId, strings, onSelect, onOpenSeq }) {
3486
3745
  )
3487
3746
  ] })
3488
3747
  ] }),
3489
- jumpError && /* @__PURE__ */ jsx10("div", { class: "mine-jump-error error", role: "alert", children: strings["mine.jump.not_found"] }),
3490
- rows && rows.length > 0 && /* @__PURE__ */ jsx10(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
3491
- isLoading && /* @__PURE__ */ jsx10("div", { class: "mine-loading", children: strings["mine.loading"] }),
3492
- error && /* @__PURE__ */ jsx10("div", { class: "error", children: error }),
3748
+ jumpError && /* @__PURE__ */ jsx11("div", { class: "mine-jump-error error", role: "alert", children: strings["mine.jump.not_found"] }),
3749
+ rows && rows.length > 0 && /* @__PURE__ */ jsx11(KpiStrip, { rows, filter, onFilter: setFilter, strings }),
3750
+ isLoading && /* @__PURE__ */ jsx11("div", { class: "mine-loading", children: strings["mine.loading"] }),
3751
+ error && /* @__PURE__ */ jsxs11("div", { class: "error", children: [
3752
+ error,
3753
+ /* @__PURE__ */ jsx11(DiagnosticHint, { api, strings, host: api.endpointHost })
3754
+ ] }),
3493
3755
  isEmpty && /* @__PURE__ */ jsxs11("div", { class: "mine-empty", children: [
3494
- /* @__PURE__ */ jsx10("strong", { children: strings["mine.empty.title"] }),
3495
- /* @__PURE__ */ jsx10("p", { children: strings["mine.empty.body"] })
3756
+ /* @__PURE__ */ jsx11("strong", { children: strings["mine.empty.title"] }),
3757
+ /* @__PURE__ */ jsx11("p", { children: strings["mine.empty.body"] })
3496
3758
  ] }),
3497
- visibleEmpty && /* @__PURE__ */ jsx10("div", { class: "mine-empty", children: /* @__PURE__ */ jsx10("p", { children: strings["mine.filter.empty"] }) }),
3498
- visibleRows && visibleRows.length > 0 && /* @__PURE__ */ jsx10("ul", { class: "mine-rows", children: visibleRows.map((row) => /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(ReportRow, { row, strings, onClick: () => onSelect(row) }) })) })
3759
+ visibleEmpty && /* @__PURE__ */ jsx11("div", { class: "mine-empty", children: /* @__PURE__ */ jsx11("p", { children: strings["mine.filter.empty"] }) }),
3760
+ visibleRows && visibleRows.length > 0 && /* @__PURE__ */ jsx11("ul", { class: "mine-rows", children: visibleRows.map((row) => /* @__PURE__ */ jsx11("li", { children: /* @__PURE__ */ jsx11(ReportRow, { row, strings, onClick: () => onSelect(row) }) })) })
3499
3761
  ] });
3500
3762
  }
3501
3763
 
3502
3764
  // src/widget/Modal.tsx
3503
3765
  import { useEffect as useEffect7, useRef as useRef6 } from "preact/hooks";
3504
- import { jsx as jsx11, jsxs as jsxs12 } from "preact/jsx-runtime";
3766
+ import { jsx as jsx12, jsxs as jsxs12 } from "preact/jsx-runtime";
3505
3767
  function Modal({ onDismiss, children, closeLabel = "Close", expanded = false }) {
3506
3768
  const modalRef = useRef6(null);
3507
3769
  const previouslyFocused = useRef6(null);
@@ -3527,7 +3789,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
3527
3789
  if (prev && typeof prev.focus === "function") prev.focus();
3528
3790
  };
3529
3791
  }, [onDismiss]);
3530
- return /* @__PURE__ */ jsx11(
3792
+ return /* @__PURE__ */ jsx12(
3531
3793
  "div",
3532
3794
  {
3533
3795
  class: `backdrop ${expanded ? "is-expanded" : ""}`,
@@ -3543,7 +3805,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
3543
3805
  role: "dialog",
3544
3806
  "aria-modal": "true",
3545
3807
  children: [
3546
- /* @__PURE__ */ jsx11(
3808
+ /* @__PURE__ */ jsx12(
3547
3809
  "button",
3548
3810
  {
3549
3811
  type: "button",
@@ -3562,7 +3824,7 @@ function Modal({ onDismiss, children, closeLabel = "Close", expanded = false })
3562
3824
  }
3563
3825
 
3564
3826
  // src/widget/PageActivityStrip.tsx
3565
- import { jsx as jsx12, jsxs as jsxs13 } from "preact/jsx-runtime";
3827
+ import { jsx as jsx13, jsxs as jsxs13 } from "preact/jsx-runtime";
3566
3828
  function PageActivityStrip({
3567
3829
  open,
3568
3830
  strings,
@@ -3571,14 +3833,14 @@ function PageActivityStrip({
3571
3833
  if (open === 0) return null;
3572
3834
  const text = open === 1 ? strings["page.strip.text.one"] : strings["page.strip.text"].replace("{n}", String(open));
3573
3835
  return /* @__PURE__ */ jsxs13("div", { class: "page-strip", children: [
3574
- /* @__PURE__ */ jsx12("span", { children: text }),
3575
- /* @__PURE__ */ jsx12("button", { type: "button", class: "page-strip-view", onClick: onView, children: strings["page.strip.view"] })
3836
+ /* @__PURE__ */ jsx13("span", { children: text }),
3837
+ /* @__PURE__ */ jsx13("button", { type: "button", class: "page-strip-view", onClick: onView, children: strings["page.strip.view"] })
3576
3838
  ] });
3577
3839
  }
3578
3840
 
3579
3841
  // src/widget/PagePeekPanel.tsx
3580
- import { useEffect as useEffect8, useState as useState7 } from "preact/hooks";
3581
- import { jsx as jsx13, jsxs as jsxs14 } from "preact/jsx-runtime";
3842
+ import { useEffect as useEffect8, useState as useState8 } from "preact/hooks";
3843
+ import { jsx as jsx14, jsxs as jsxs14 } from "preact/jsx-runtime";
3582
3844
  function PagePeekPanel({
3583
3845
  api,
3584
3846
  externalId,
@@ -3589,8 +3851,8 @@ function PagePeekPanel({
3589
3851
  onPickRow,
3590
3852
  onSend
3591
3853
  }) {
3592
- const [rows, setRows] = useState7(null);
3593
- const [failed, setFailed] = useState7(false);
3854
+ const [rows, setRows] = useState8(null);
3855
+ const [failed, setFailed] = useState8(false);
3594
3856
  useEffect8(() => {
3595
3857
  let cancelled = false;
3596
3858
  const pagePath = currentPagePath(getCurrentPage !== void 0 ? { getCurrentPage } : {});
@@ -3607,11 +3869,11 @@ function PagePeekPanel({
3607
3869
  };
3608
3870
  }, [api, externalId]);
3609
3871
  return /* @__PURE__ */ jsxs14("div", { class: "page-peek", role: "region", "aria-label": strings["page.peek.title"], children: [
3610
- /* @__PURE__ */ jsx13("div", { class: "page-peek-title", children: strings["page.peek.title"] }),
3872
+ /* @__PURE__ */ jsx14("div", { class: "page-peek-title", children: strings["page.peek.title"] }),
3611
3873
  rows === null && !failed && /* @__PURE__ */ jsxs14("div", { class: "page-peek-skeleton", "aria-hidden": "true", children: [
3612
- /* @__PURE__ */ jsx13("div", {}),
3874
+ /* @__PURE__ */ jsx14("div", {}),
3613
3875
  " ",
3614
- /* @__PURE__ */ jsx13("div", {})
3876
+ /* @__PURE__ */ jsx14("div", {})
3615
3877
  ] }),
3616
3878
  rows !== null && rows.map((row) => /* @__PURE__ */ jsxs14(
3617
3879
  "button",
@@ -3620,49 +3882,51 @@ function PagePeekPanel({
3620
3882
  class: "page-peek-row",
3621
3883
  onClick: () => onPickRow?.(row.id),
3622
3884
  children: [
3623
- /* @__PURE__ */ jsx13("span", { class: `page-peek-dot status-${row.status}`, "aria-hidden": "true" }),
3624
- /* @__PURE__ */ jsx13("span", { class: "page-peek-desc", children: row.description })
3885
+ /* @__PURE__ */ jsx14("span", { class: `page-peek-dot status-${row.status}`, "aria-hidden": "true" }),
3886
+ /* @__PURE__ */ jsx14("span", { class: "page-peek-desc", children: row.description })
3625
3887
  ]
3626
3888
  },
3627
3889
  row.id
3628
3890
  )),
3629
3891
  /* @__PURE__ */ jsxs14("div", { class: "page-peek-footer", children: [
3630
- /* @__PURE__ */ jsx13("button", { type: "button", class: "page-peek-viewall", onClick: onViewAll, children: open === 1 ? strings["page.peek.viewAll.one"] : strings["page.peek.viewAll"].replace("{n}", String(open)) }),
3631
- /* @__PURE__ */ jsx13("button", { type: "button", class: "page-peek-send", onClick: onSend, children: strings["fab.label"] })
3892
+ /* @__PURE__ */ jsx14("button", { type: "button", class: "page-peek-viewall", onClick: onViewAll, children: open === 1 ? strings["page.peek.viewAll.one"] : strings["page.peek.viewAll"].replace("{n}", String(open)) }),
3893
+ /* @__PURE__ */ jsx14("button", { type: "button", class: "page-peek-send", onClick: onSend, children: strings["fab.label"] })
3632
3894
  ] })
3633
3895
  ] });
3634
3896
  }
3635
3897
 
3636
3898
  // src/widget/pageSignal.ts
3637
- import { useCallback, useEffect as useEffect9, useState as useState8 } from "preact/hooks";
3638
- var TTL_MS = 6e4;
3899
+ import { useCallback, useEffect as useEffect9, useState as useState9 } from "preact/hooks";
3900
+ var TTL_MS2 = 6e4;
3639
3901
  function computeOpenCount(kpis) {
3640
3902
  const s = kpis.by_status;
3641
3903
  return (s.new ?? 0) + (s.in_progress ?? 0) + (s.awaiting_validation ?? 0);
3642
3904
  }
3643
- var cache = /* @__PURE__ */ new Map();
3905
+ var cache2 = /* @__PURE__ */ new Map();
3644
3906
  var inflight = /* @__PURE__ */ new Map();
3645
3907
  function invalidatePageSignal() {
3646
- cache.clear();
3908
+ cache2.clear();
3647
3909
  }
3648
- function fetchOpen(api, externalId, path) {
3910
+ function fetchPageKpis(api, externalId, path) {
3649
3911
  const key = `${externalId}|${path}`;
3650
- const hit = cache.get(key);
3651
- if (hit && Date.now() - hit.fetchedAt < TTL_MS) return Promise.resolve(hit.open);
3912
+ const hit = cache2.get(key);
3913
+ if (hit && Date.now() - hit.fetchedAt < TTL_MS2) return Promise.resolve(hit.kpis);
3652
3914
  const pending = inflight.get(key);
3653
3915
  if (pending) return pending;
3654
3916
  const p = api.fetchBoardKpis(externalId, { pagePath: path }).then((kpis) => {
3655
- const open = computeOpenCount(kpis);
3656
- cache.set(key, { open, fetchedAt: Date.now() });
3657
- return open;
3917
+ cache2.set(key, { kpis, fetchedAt: Date.now() });
3918
+ return kpis;
3658
3919
  }).finally(() => inflight.delete(key));
3659
3920
  inflight.set(key, p);
3660
3921
  return p;
3661
3922
  }
3923
+ function fetchOpen(api, externalId, path) {
3924
+ return fetchPageKpis(api, externalId, path).then(computeOpenCount);
3925
+ }
3662
3926
  function usePageOpenCount(opts) {
3663
3927
  const { api, externalId, getCurrentPage, enabled } = opts;
3664
- const [open, setOpen] = useState8(0);
3665
- const [tick, setTick] = useState8(0);
3928
+ const [open, setOpen] = useState9(0);
3929
+ const [tick, setTick] = useState9(0);
3666
3930
  const refresh = useCallback(() => {
3667
3931
  invalidatePageSignal();
3668
3932
  setTick((t) => t + 1);
@@ -5452,6 +5716,20 @@ button.report-detail-edit {
5452
5716
  min-height: 0;
5453
5717
  overflow: hidden;
5454
5718
  }
5719
+ /* Stale-while-revalidate: a query change keeps the previous rows visible
5720
+ * (locally pre-filtered where the row data allows) and dims them while the
5721
+ * server confirms \u2014 the instant local response is the #352 "never inert"
5722
+ * guarantee, the dim is the "still confirming" affordance. */
5723
+ .board-view.is-stale .board-list,
5724
+ .board-view.is-stale .board-list-count,
5725
+ .board-view.is-stale .board-kpi-strip {
5726
+ opacity: 0.55;
5727
+ transition: opacity 0.15s ease;
5728
+ }
5729
+ .board-load-more {
5730
+ margin: 4px auto 8px;
5731
+ flex: 0 0 auto;
5732
+ }
5455
5733
  .board-list-count {
5456
5734
  font-size: var(--mfb-text-xs);
5457
5735
  color: var(--mfb-text-muted);
@@ -5743,7 +6021,7 @@ button.report-detail-edit {
5743
6021
  `;
5744
6022
 
5745
6023
  // src/widget/mount.tsx
5746
- import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs15 } from "preact/jsx-runtime";
6024
+ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs15 } from "preact/jsx-runtime";
5747
6025
  function computeFabVisible(opts, externalId, visibilityAllowed) {
5748
6026
  if (!opts.showFAB) return false;
5749
6027
  if (opts.getExternalId !== void 0 && !externalId) return false;
@@ -5773,7 +6051,7 @@ function mountWidget(options) {
5773
6051
  function openWidget(externalId) {
5774
6052
  rerender({ ...currentState, open: true, tab: "send" });
5775
6053
  if (options.openToCurrentPageFeedback && options.api && externalId) {
5776
- options.api.fetchBoardKpis(externalId, { pagePath: currentPagePath(options) }).then((kpis) => {
6054
+ fetchPageKpis(options.api, externalId, currentPagePath(options)).then((kpis) => {
5777
6055
  if (kpis.total > 0 && currentState.open && currentState.tab === "send") {
5778
6056
  rerender({ ...currentState, tab: "board" });
5779
6057
  }
@@ -5841,7 +6119,7 @@ function mountWidget(options) {
5841
6119
  enabled: pageActivityEnabled
5842
6120
  });
5843
6121
  const fabLabel = pageSignal.open === 1 ? options.strings["page.badge.aria.one"] : pageSignal.open > 0 ? options.strings["page.badge.aria"].replace("{n}", String(pageSignal.open)) : options.strings["fab.label"];
5844
- const [visibilityAllowed, setVisibilityAllowed] = useState9(
6122
+ const [visibilityAllowed, setVisibilityAllowed] = useState10(
5845
6123
  !options.requiresVisibilityCheck
5846
6124
  );
5847
6125
  useEffect10(() => {
@@ -5865,7 +6143,7 @@ function mountWidget(options) {
5865
6143
  }, [externalId]);
5866
6144
  const fabVisible = computeFabVisible(options, externalId, visibilityAllowed);
5867
6145
  const showMineTab = Boolean(options.api && externalId);
5868
- const [peekOpen, setPeekOpen] = useState9(false);
6146
+ const [peekOpen, setPeekOpen] = useState10(false);
5869
6147
  const peekTimers = useRef7({});
5870
6148
  const peekEnter = () => {
5871
6149
  if (peekTimers.current.close) clearTimeout(peekTimers.current.close);
@@ -5889,7 +6167,7 @@ function mountWidget(options) {
5889
6167
  onFocusOut: () => setPeekOpen(false),
5890
6168
  onKeyDown: (e) => e.key === "Escape" && setPeekOpen(false),
5891
6169
  children: [
5892
- peekOpen && !state.open && pageActivityEnabled && pageSignal.open > 0 && options.api && externalId && /* @__PURE__ */ jsx14(
6170
+ peekOpen && !state.open && pageActivityEnabled && pageSignal.open > 0 && options.api && externalId && /* @__PURE__ */ jsx15(
5893
6171
  PagePeekPanel,
5894
6172
  {
5895
6173
  api: options.api,
@@ -5913,7 +6191,7 @@ function mountWidget(options) {
5913
6191
  }
5914
6192
  }
5915
6193
  ),
5916
- /* @__PURE__ */ jsx14(
6194
+ /* @__PURE__ */ jsx15(
5917
6195
  Fab,
5918
6196
  {
5919
6197
  label: fabLabel,
@@ -5945,11 +6223,11 @@ function mountWidget(options) {
5945
6223
  closeLabel: options.strings["form.close"],
5946
6224
  expanded: state.tab === "board",
5947
6225
  children: [
5948
- state.confirmingDiscard && /* @__PURE__ */ jsx14("div", { class: "discard-confirm", role: "alertdialog", "aria-modal": "true", children: /* @__PURE__ */ jsxs15("div", { class: "discard-confirm-card", children: [
5949
- /* @__PURE__ */ jsx14("p", { class: "discard-confirm-title", children: options.strings["form.discard.title"] }),
5950
- /* @__PURE__ */ jsx14("p", { class: "discard-confirm-body", children: options.strings["form.discard.body"] }),
6226
+ state.confirmingDiscard && /* @__PURE__ */ jsx15("div", { class: "discard-confirm", role: "alertdialog", "aria-modal": "true", children: /* @__PURE__ */ jsxs15("div", { class: "discard-confirm-card", children: [
6227
+ /* @__PURE__ */ jsx15("p", { class: "discard-confirm-title", children: options.strings["form.discard.title"] }),
6228
+ /* @__PURE__ */ jsx15("p", { class: "discard-confirm-body", children: options.strings["form.discard.body"] }),
5951
6229
  /* @__PURE__ */ jsxs15("div", { class: "discard-confirm-actions", children: [
5952
- /* @__PURE__ */ jsx14(
6230
+ /* @__PURE__ */ jsx15(
5953
6231
  "button",
5954
6232
  {
5955
6233
  type: "button",
@@ -5958,7 +6236,7 @@ function mountWidget(options) {
5958
6236
  children: options.strings["form.discard.keep"]
5959
6237
  }
5960
6238
  ),
5961
- /* @__PURE__ */ jsx14(
6239
+ /* @__PURE__ */ jsx15(
5962
6240
  "button",
5963
6241
  {
5964
6242
  type: "button",
@@ -5980,7 +6258,7 @@ function mountWidget(options) {
5980
6258
  ] })
5981
6259
  ] }) }),
5982
6260
  showMineTab && /* @__PURE__ */ jsxs15("div", { class: "tab-strip", role: "tablist", children: [
5983
- /* @__PURE__ */ jsx14(
6261
+ /* @__PURE__ */ jsx15(
5984
6262
  "button",
5985
6263
  {
5986
6264
  type: "button",
@@ -5991,7 +6269,7 @@ function mountWidget(options) {
5991
6269
  children: options.strings["tab.send"]
5992
6270
  }
5993
6271
  ),
5994
- /* @__PURE__ */ jsx14(
6272
+ /* @__PURE__ */ jsx15(
5995
6273
  "button",
5996
6274
  {
5997
6275
  type: "button",
@@ -6002,7 +6280,7 @@ function mountWidget(options) {
6002
6280
  children: options.strings["tab.mine"]
6003
6281
  }
6004
6282
  ),
6005
- /* @__PURE__ */ jsx14(
6283
+ /* @__PURE__ */ jsx15(
6006
6284
  "button",
6007
6285
  {
6008
6286
  type: "button",
@@ -6013,7 +6291,7 @@ function mountWidget(options) {
6013
6291
  children: options.strings["tab.changelog"]
6014
6292
  }
6015
6293
  ),
6016
- /* @__PURE__ */ jsx14(
6294
+ /* @__PURE__ */ jsx15(
6017
6295
  "button",
6018
6296
  {
6019
6297
  type: "button",
@@ -6026,7 +6304,7 @@ function mountWidget(options) {
6026
6304
  )
6027
6305
  ] }),
6028
6306
  state.tab === "send" && /* @__PURE__ */ jsxs15(Fragment3, { children: [
6029
- showMineTab && pageActivityEnabled && /* @__PURE__ */ jsx14(
6307
+ showMineTab && pageActivityEnabled && /* @__PURE__ */ jsx15(
6030
6308
  PageActivityStrip,
6031
6309
  {
6032
6310
  open: pageSignal.open,
@@ -6034,7 +6312,7 @@ function mountWidget(options) {
6034
6312
  onView: () => rerender(clearSelected({ ...currentState, tab: "board" }))
6035
6313
  }
6036
6314
  ),
6037
- /* @__PURE__ */ jsx14(
6315
+ /* @__PURE__ */ jsx15(
6038
6316
  Form,
6039
6317
  {
6040
6318
  strings: options.strings,
@@ -6051,7 +6329,7 @@ function mountWidget(options) {
6051
6329
  }
6052
6330
  )
6053
6331
  ] }),
6054
- state.tab === "mine" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx14(
6332
+ state.tab === "mine" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx15(
6055
6333
  MineList,
6056
6334
  {
6057
6335
  api: options.api,
@@ -6061,7 +6339,7 @@ function mountWidget(options) {
6061
6339
  onOpenSeq: openSeq
6062
6340
  }
6063
6341
  ),
6064
- (state.tab === "mine" || state.tab === "changelog") && options.api && externalId && state.selectedReportId && /* @__PURE__ */ jsx14(
6342
+ (state.tab === "mine" || state.tab === "changelog") && options.api && externalId && state.selectedReportId && /* @__PURE__ */ jsx15(
6065
6343
  ReportDetailView,
6066
6344
  {
6067
6345
  api: options.api,
@@ -6073,7 +6351,7 @@ function mountWidget(options) {
6073
6351
  ...buildPermalink && { buildPermalink }
6074
6352
  }
6075
6353
  ),
6076
- state.tab === "changelog" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx14(
6354
+ state.tab === "changelog" && options.api && externalId && !state.selectedReportId && /* @__PURE__ */ jsx15(
6077
6355
  ChangelogList,
6078
6356
  {
6079
6357
  api: options.api,
@@ -6084,7 +6362,7 @@ function mountWidget(options) {
6084
6362
  ),
6085
6363
  state.tab === "board" && options.api && externalId && // BoardView owns its own master/detail navigation — no
6086
6364
  // selectedReportId routing through the modal-level state.
6087
- /* @__PURE__ */ jsx14(
6365
+ /* @__PURE__ */ jsx15(
6088
6366
  BoardView,
6089
6367
  {
6090
6368
  api: options.api,
@@ -6205,8 +6483,8 @@ function createFeedback(config) {
6205
6483
  capture_method: captureMethod,
6206
6484
  technical_context
6207
6485
  };
6208
- if ("0.31.0") {
6209
- payload.widget_version = "0.31.0";
6486
+ if ("0.33.0") {
6487
+ payload.widget_version = "0.33.0";
6210
6488
  }
6211
6489
  if (manualScreenshots?.length) {
6212
6490
  payload.screenshots = manualScreenshots;
@@ -6332,4 +6610,4 @@ function createFeedback(config) {
6332
6610
  export {
6333
6611
  createFeedback
6334
6612
  };
6335
- //# sourceMappingURL=chunk-AA24TFKX.mjs.map
6613
+ //# sourceMappingURL=chunk-RR6UB75B.mjs.map