@youasked/react 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,15 @@
1
1
  "use client";
2
2
 
3
3
  // src/YouAsked.tsx
4
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
4
+ import { useCallback as useCallback3, useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4, useSyncExternalStore } from "react";
5
5
  import { createPortal } from "react-dom";
6
6
 
7
7
  // src/client.ts
8
8
  var YouAskedError = class extends Error {
9
- constructor(code) {
9
+ constructor(code, detail) {
10
10
  super(code);
11
11
  this.code = code;
12
+ this.detail = detail;
12
13
  }
13
14
  };
14
15
  var ANON_KEY = "youasked:anon";
@@ -48,14 +49,14 @@ function createClient({ appKey, apiUrl, user, signature }) {
48
49
  if (json) h["content-type"] = "application/json";
49
50
  return h;
50
51
  }
51
- async function call(path, body, raw) {
52
+ async function call(path, body, raw, method) {
52
53
  const res = await fetch(base2 + path, {
53
- method: body === void 0 && !raw ? "GET" : "POST",
54
+ method: method ?? (body === void 0 && !raw ? "GET" : "POST"),
54
55
  headers: raw ? { ...headers(false), "content-type": raw.type } : headers(body !== void 0),
55
56
  body: raw ?? (body === void 0 ? void 0 : JSON.stringify(body))
56
57
  });
57
58
  const data = await res.json().catch(() => ({}));
58
- if (!res.ok) throw new YouAskedError(data.error ?? `http_${res.status}`);
59
+ if (!res.ok) throw new YouAskedError(data.error ?? `http_${res.status}`, data.message);
59
60
  return data;
60
61
  }
61
62
  return {
@@ -68,6 +69,7 @@ function createClient({ appKey, apiUrl, user, signature }) {
68
69
  attachments: d.attachments ?? []
69
70
  })),
70
71
  upload: (image) => call("/uploads", void 0, image),
72
+ remove: (id) => call(`/requests/${encodeURIComponent(id)}`, void 0, void 0, "DELETE"),
71
73
  earlyAccess: (id) => call(`/requests/${encodeURIComponent(id)}/early-access`, {}),
72
74
  whatsNewSeen: () => call("/whats-new/seen", {}),
73
75
  vote: (id) => call(`/requests/${encodeURIComponent(id)}/vote`, {}),
@@ -118,7 +120,7 @@ var DAY = 24 * 60 * MIN;
118
120
  var uploads = /* @__PURE__ */ new Map();
119
121
  var wait = (ms = 380) => new Promise((r) => setTimeout(r, ms + Math.random() * ms * 0.5));
120
122
  var WORDS = (s) => new Set(s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 2));
121
- function createDemoClient(appName = "Acme") {
123
+ function createDemoClient(appName = "this app") {
122
124
  const t0 = Date.now();
123
125
  let seq = 100;
124
126
  const listeners = /* @__PURE__ */ new Set();
@@ -145,31 +147,31 @@ function createDemoClient(appName = "Acme") {
145
147
  });
146
148
  const team = (id, body, ago2) => ({ id, body, team: true, author: null, mine: false, createdAt: t0 - ago2 });
147
149
  const store = [
148
- seed(12, "Split a bill with my partner", "We share groceries and rent. Would love to tag an expense as 50/50 and see who owes who.", 14, "planned", {
150
+ seed(12, "Dark mode", "Easier on the eyes at night, and it'd match the rest of my phone.", 14, "planned", {
149
151
  comments_: [
150
- { id: "c1", body: "Yes! Even just splitting one expense would help.", team: false, author: "Raj", mine: false, createdAt: t0 - 3 * DAY },
151
- team("c2", "On the list for December. Would a simple \u201Cwho owes who\u201D total be enough to start?", 2 * DAY)
152
+ { id: "c1", body: "Yes please. I use it every night.", team: false, author: "Raj", mine: false, createdAt: t0 - 3 * DAY },
153
+ team("c2", "On the list for December. Would following your phone's setting be enough to start?", 2 * DAY)
152
154
  ]
153
155
  }),
154
- seed(13, "Monthly budget per category", "Set $400 for food and get a heads-up when I'm close.", 11, "in_progress"),
155
- seed(7, "Dark mode for the charts", "", 8, "shipped", {
156
- shipNote: "Charts now follow your theme. Settings \u2192 Appearance.",
156
+ seed(13, "Export to CSV", "So I can open everything in a spreadsheet.", 11, "in_progress"),
157
+ seed(7, "Keyboard shortcuts", "", 8, "shipped", {
158
+ shipNote: "Press ? anywhere to see them all.",
157
159
  shippedAt: t0 - 2 * DAY,
158
160
  creditName: "Sam"
159
161
  }),
160
- seed(14, "Import my bank statement PDF", "Some of my older expenses never came through email.", 6, "open"),
161
- seed(15, "Recurring expenses (Netflix, rent)", "", 4, "open"),
162
- seed(16, "Widget for the home screen", "Just today's total would be enough.", 3, "open"),
163
- seed(9, "Show which card I paid with", "", 5, "shipped", {
164
- shipNote: "Each expense now shows the card's last 4 digits.",
162
+ seed(14, "Offline mode", "I use it on the train and keep losing signal.", 6, "open"),
163
+ seed(15, "Share a link with a friend", "", 4, "open"),
164
+ seed(16, "Widget for the home screen", "Just today's number would be enough.", 3, "open"),
165
+ seed(9, "Sign in with Apple", "", 5, "shipped", {
166
+ shipNote: "You can now sign in with your Apple ID.",
165
167
  shippedAt: t0 - 4 * 60 * MIN,
166
168
  mine: true,
167
169
  voted: true
168
170
  }),
169
- seed(17, "Export expenses to Excel", "For my accountant at tax time.", 2, "open", {
171
+ seed(17, "A reminder the day before", "A nudge the day before something is due would do.", 2, "open", {
170
172
  mine: true,
171
173
  voted: true,
172
- comments_: [team("c3", "Good one. Would a CSV that opens in Excel work, or do you need .xlsx?", 3 * 60 * MIN)]
174
+ comments_: [team("c3", "Good one. Would a push notification work, or would you rather get an email?", 3 * 60 * MIN)]
173
175
  })
174
176
  ];
175
177
  const find = (id) => store.find((r) => r.id === id);
@@ -234,7 +236,20 @@ function createDemoClient(appName = "Acme") {
234
236
  await wait();
235
237
  const r = find(id);
236
238
  if (!r) throw new YouAskedError("not_found");
237
- return { request: view(r), comments: [...r.comments_], attachments: [...r.attachments_], earlyAccess: r.early };
239
+ const othersJoined = r.votes > (r.mine && r.voted ? 1 : 0) || r.comments_.some((c) => !c.mine);
240
+ return {
241
+ request: view(r),
242
+ comments: [...r.comments_],
243
+ attachments: [...r.attachments_],
244
+ earlyAccess: r.early,
245
+ canDelete: Boolean(r.mine) && (r.status === "open" || r.status === "pending") && !othersJoined
246
+ };
247
+ },
248
+ async remove(id) {
249
+ await wait();
250
+ const i = store.findIndex((r) => r.id === id);
251
+ if (i >= 0) store.splice(i, 1);
252
+ return { ok: true };
238
253
  },
239
254
  async create(input) {
240
255
  await wait(650);
@@ -329,11 +344,120 @@ var IconSpark = () => /* @__PURE__ */ jsxs("svg", { ...base, children: [
329
344
  /* @__PURE__ */ jsx("path", { d: "m12 3 1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9Z" }),
330
345
  /* @__PURE__ */ jsx("path", { d: "M19 16v4M17 18h4" })
331
346
  ] });
332
- var IconIdea = () => /* @__PURE__ */ jsxs("svg", { ...base, children: [
333
- /* @__PURE__ */ jsx("path", { d: "M12 3.5c-4.97 0-9 3.47-9 7.75 0 2.3 1.17 4.37 3.03 5.8L5.2 20.5l4.1-2.06c.86.2 1.76.31 2.7.31 4.97 0 9-3.47 9-7.75S16.97 3.5 12 3.5Z" }),
334
- /* @__PURE__ */ jsx("path", { d: "M8.2 13.2 12 9.4l3.8 3.8", strokeWidth: 2.6 })
347
+ var IconIdea = ({ ink = "var(--_bg)" }) => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [
348
+ /* @__PURE__ */ jsx("path", { d: "M6 2.25h12a4.5 4.5 0 0 1 4.5 4.5V15a4.5 4.5 0 0 1-4.5 4.5h-6.75L6 23.25V19.5A4.5 4.5 0 0 1 1.5 15V6.75A4.5 4.5 0 0 1 6 2.25Z", fill: "currentColor" }),
349
+ /* @__PURE__ */ jsx("path", { d: "M7.9 13.9 12 9.8l4.1 4.1", fill: "none", stroke: ink, strokeWidth: 2.6, strokeLinecap: "round", strokeLinejoin: "round" })
335
350
  ] });
336
351
 
352
+ // src/nudge.tsx
353
+ import { useCallback, useEffect, useRef, useState } from "react";
354
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
355
+ var NUDGE_DEFAULTS = { everyDays: 7, activeSeconds: 30, skipVisits: 1, message: null, position: "bottom" };
356
+ var DAY2 = 24 * 60 * 60 * 1e3;
357
+ var VISIT_GAP = 30 * 60 * 1e3;
358
+ function resolveNudge(server, prop) {
359
+ if (prop === false) return null;
360
+ if (typeof prop === "object") return { ...NUDGE_DEFAULTS, ...server, ...prop };
361
+ if (prop === true) return server ?? NUDGE_DEFAULTS;
362
+ return server ?? null;
363
+ }
364
+ function load(key) {
365
+ const empty = { visits: 0, lastVisitAt: 0, lastShownAt: 0, lastEngagedAt: 0, dismissals: 0 };
366
+ if (!key) return empty;
367
+ try {
368
+ return { ...empty, ...JSON.parse(localStorage.getItem(key) ?? "{}") };
369
+ } catch {
370
+ return empty;
371
+ }
372
+ }
373
+ function save(key, m) {
374
+ if (!key) return;
375
+ try {
376
+ localStorage.setItem(key, JSON.stringify(m));
377
+ } catch {
378
+ }
379
+ }
380
+ function useNudge(settings, storageKey, blocked) {
381
+ const memory = useRef(null);
382
+ const [show, setShow] = useState(false);
383
+ useEffect(() => {
384
+ if (!settings || memory.current) return;
385
+ const m = load(storageKey);
386
+ const now = Date.now();
387
+ if (now - m.lastVisitAt > VISIT_GAP) m.visits += 1;
388
+ m.lastVisitAt = now;
389
+ memory.current = m;
390
+ save(storageKey, m);
391
+ }, [settings, storageKey]);
392
+ useEffect(() => {
393
+ if (!settings || show) return;
394
+ let active = 0;
395
+ const timer = window.setInterval(() => {
396
+ if (document.visibilityState !== "visible" || blocked) return;
397
+ active += 1;
398
+ if (active < settings.activeSeconds) return;
399
+ window.clearInterval(timer);
400
+ const m = memory.current;
401
+ if (!m) return;
402
+ const now = Date.now();
403
+ const wait2 = settings.everyDays * DAY2 * 2 ** Math.min(m.dismissals, 3);
404
+ const due = !storageKey || m.visits > settings.skipVisits && now - m.lastShownAt >= wait2 && now - m.lastEngagedAt >= wait2;
405
+ if (!due) return;
406
+ m.lastShownAt = now;
407
+ save(storageKey, m);
408
+ setShow(true);
409
+ }, 1e3);
410
+ return () => window.clearInterval(timer);
411
+ }, [settings, storageKey, blocked, show]);
412
+ const engaged = useCallback(() => {
413
+ const m = memory.current;
414
+ if (m) {
415
+ m.lastEngagedAt = Date.now();
416
+ m.dismissals = 0;
417
+ save(storageKey, m);
418
+ }
419
+ setShow(false);
420
+ }, [storageKey]);
421
+ const dismiss = useCallback(() => {
422
+ const m = memory.current;
423
+ if (m) {
424
+ m.dismissals += 1;
425
+ save(storageKey, m);
426
+ }
427
+ setShow(false);
428
+ }, [storageKey]);
429
+ return { show, engaged, dismiss };
430
+ }
431
+ function NudgeToast({
432
+ settings,
433
+ appName,
434
+ position,
435
+ trigger,
436
+ onOpen,
437
+ onDismiss
438
+ }) {
439
+ return /* @__PURE__ */ jsxs2("div", { className: "ya-toast ya-nudge", role: "status", "data-pos": position, "data-trigger": trigger, "data-place": settings.position, children: [
440
+ /* @__PURE__ */ jsxs2("div", { className: "ya-toast-top", children: [
441
+ /* @__PURE__ */ jsxs2("p", { className: "ya-toast-kicker", children: [
442
+ "Help shape ",
443
+ appName
444
+ ] }),
445
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx2(IconClose, {}) })
446
+ ] }),
447
+ /* @__PURE__ */ jsxs2("div", { className: "ya-nudge-body", children: [
448
+ /* @__PURE__ */ jsx2("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(IconIdea, {}) }),
449
+ /* @__PURE__ */ jsxs2("div", { children: [
450
+ /* @__PURE__ */ jsx2("p", { className: "ya-toast-title", children: settings.message ?? `Got an idea for ${appName}?` }),
451
+ /* @__PURE__ */ jsx2("p", { className: "ya-toast-note", children: "Ask for a feature or vote on what others want." })
452
+ ] })
453
+ ] }),
454
+ /* @__PURE__ */ jsxs2("div", { className: "ya-toast-actions", children: [
455
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "Share an idea" }),
456
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Not now" })
457
+ ] })
458
+ ] });
459
+ }
460
+
337
461
  // src/styles.ts
338
462
  var css = `
339
463
  .ya-scope{--_bg:var(--ya-bg);--_fg:var(--ya-fg);--_accent:var(--ya-accent);--_accent-fg:var(--ya-accent-fg);
@@ -392,6 +516,9 @@ box-shadow:0 0 0 1px var(--_border),0 24px 70px rgba(0,0,0,.22);padding-bottom:0
392
516
  .ya-link{display:inline-flex;align-items:center;gap:2px;font-size:14px;font-weight:600;color:var(--_accent);padding:6px 8px;border-radius:10px;flex:none}
393
517
  .ya-link svg{width:15px;height:15px}
394
518
  .ya-link:hover{background:var(--_tint)}
519
+ .ya-delete-row{display:flex;align-items:center;flex-wrap:wrap;gap:4px}
520
+ .ya-link-quiet{color:var(--_muted);font-weight:500;padding-left:0}
521
+ .ya-link-danger{color:#e5484d}
395
522
  .ya-avatar{display:block;flex:none;border-radius:24%;object-fit:cover;background:var(--_surface)}
396
523
 
397
524
  /* ---------- body, lists */
@@ -561,6 +688,10 @@ box-shadow:0 0 0 1px var(--_border),0 24px 70px rgba(0,0,0,.22);padding-bottom:0
561
688
  .ya-toast .ya-live{margin-top:8px}
562
689
  .ya-live svg{width:15px;height:15px}
563
690
  .ya-toast-note{font-size:13.5px;color:var(--_muted);margin-top:6px;overflow-wrap:anywhere}
691
+ .ya-toast[data-place=top]{top:calc(16px + env(safe-area-inset-top));bottom:auto}
692
+ .ya-nudge-body{display:flex;gap:12px;align-items:flex-start;margin-top:6px}
693
+ .ya-nudge-body .ya-card-icon{flex:none;margin-top:2px}
694
+ .ya-nudge-body .ya-toast-title{margin-top:0}
564
695
  .ya-toast-actions{display:flex;gap:8px;margin-top:12px}
565
696
  .ya-toast-quote{color:var(--_fg);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
566
697
  .ya-toast .ya-icon-btn{width:30px;height:30px;margin:-6px -6px 0 0;background:none}
@@ -746,13 +877,13 @@ function tokenStyle(t) {
746
877
  }
747
878
 
748
879
  // src/shared.tsx
749
- import { useCallback, useEffect, useRef, useState } from "react";
750
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
880
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
881
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
751
882
  function useVote(client, initial) {
752
- const [state, setState] = useState({ voted: initial.voted, votes: initial.votes });
753
- const busy = useRef(false);
754
- useEffect(() => setState({ voted: initial.voted, votes: initial.votes }), [initial.voted, initial.votes]);
755
- const toggle = useCallback(async () => {
883
+ const [state, setState] = useState2({ voted: initial.voted, votes: initial.votes });
884
+ const busy = useRef2(false);
885
+ useEffect2(() => setState({ voted: initial.voted, votes: initial.votes }), [initial.voted, initial.votes]);
886
+ const toggle = useCallback2(async () => {
756
887
  if (busy.current) return;
757
888
  busy.current = true;
758
889
  const before = state;
@@ -769,7 +900,7 @@ function useVote(client, initial) {
769
900
  }
770
901
  function VoteButton({ client, request, compact }) {
771
902
  const { voted, votes, toggle } = useVote(client, request);
772
- return /* @__PURE__ */ jsxs2(
903
+ return /* @__PURE__ */ jsxs3(
773
904
  "button",
774
905
  {
775
906
  type: "button",
@@ -781,7 +912,7 @@ function VoteButton({ client, request, compact }) {
781
912
  toggle();
782
913
  },
783
914
  children: [
784
- /* @__PURE__ */ jsx2(IconUp, {}),
915
+ /* @__PURE__ */ jsx3(IconUp, {}),
785
916
  votes
786
917
  ]
787
918
  }
@@ -793,9 +924,9 @@ function BigVote({
793
924
  onChange
794
925
  }) {
795
926
  const { voted, votes, toggle } = useVote(client, request);
796
- useEffect(() => onChange?.(voted), [voted, onChange]);
797
- return /* @__PURE__ */ jsxs2("button", { type: "button", className: "ya-bigvote", "aria-pressed": voted, onClick: toggle, children: [
798
- /* @__PURE__ */ jsx2(IconUp, {}),
927
+ useEffect2(() => onChange?.(voted), [voted, onChange]);
928
+ return /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-bigvote", "aria-pressed": voted, onClick: toggle, children: [
929
+ /* @__PURE__ */ jsx3(IconUp, {}),
799
930
  voted ? "Voted" : "Upvote",
800
931
  " \xB7 ",
801
932
  votes
@@ -806,11 +937,12 @@ var STATUS_LABEL = {
806
937
  planned: "Planned",
807
938
  in_progress: "In progress",
808
939
  shipped: "Shipped",
809
- declined: "Not planned"
940
+ declined: "Not planned",
941
+ pending: "Awaiting approval"
810
942
  };
811
943
  function StatusPill({ status }) {
812
- return /* @__PURE__ */ jsxs2("span", { className: "ya-pill", "data-s": status, children: [
813
- status === "shipped" && /* @__PURE__ */ jsx2(IconCheck, {}),
944
+ return /* @__PURE__ */ jsxs3("span", { className: "ya-pill", "data-s": status, children: [
945
+ status === "shipped" && /* @__PURE__ */ jsx3(IconCheck, {}),
814
946
  STATUS_LABEL[status]
815
947
  ] });
816
948
  }
@@ -820,45 +952,48 @@ function RequestRow({
820
952
  onOpen,
821
953
  compact
822
954
  }) {
823
- return /* @__PURE__ */ jsxs2("li", { className: compact ? "ya-row ya-row-sm" : "ya-row", children: [
824
- /* @__PURE__ */ jsx2(VoteButton, { client, request, compact }),
825
- /* @__PURE__ */ jsxs2("button", { type: "button", className: "ya-row-main", onClick: onOpen, children: [
826
- /* @__PURE__ */ jsx2("span", { className: "ya-row-title", children: request.title }),
827
- (request.status !== "open" || request.comments > 0 || request.mine) && /* @__PURE__ */ jsxs2("span", { className: "ya-meta", children: [
828
- request.status !== "open" && /* @__PURE__ */ jsx2(StatusPill, { status: request.status }),
829
- request.comments > 0 && /* @__PURE__ */ jsxs2("span", { children: [
830
- /* @__PURE__ */ jsx2(IconChat, {}),
955
+ return /* @__PURE__ */ jsxs3("li", { className: compact ? "ya-row ya-row-sm" : "ya-row", children: [
956
+ /* @__PURE__ */ jsx3(VoteButton, { client, request, compact }),
957
+ /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-row-main", onClick: onOpen, children: [
958
+ /* @__PURE__ */ jsx3("span", { className: "ya-row-title", children: request.title }),
959
+ (request.status !== "open" || request.comments > 0 || request.mine) && /* @__PURE__ */ jsxs3("span", { className: "ya-meta", children: [
960
+ request.status !== "open" && /* @__PURE__ */ jsx3(StatusPill, { status: request.status }),
961
+ request.comments > 0 && /* @__PURE__ */ jsxs3("span", { children: [
962
+ /* @__PURE__ */ jsx3(IconChat, {}),
831
963
  request.comments
832
964
  ] }),
833
- request.mine && /* @__PURE__ */ jsx2("span", { children: "You asked" })
965
+ request.mine && /* @__PURE__ */ jsx3("span", { children: "You asked" })
834
966
  ] })
835
967
  ] })
836
968
  ] });
837
969
  }
838
970
  function Skeleton({ rows = 3 }) {
839
- return /* @__PURE__ */ jsx2("div", { className: "ya-list", "aria-label": "Loading", role: "status", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx2("div", { className: "ya-skel" }, i)) });
971
+ return /* @__PURE__ */ jsx3("div", { className: "ya-list", "aria-label": "Loading", role: "status", children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsx3("div", { className: "ya-skel" }, i)) });
840
972
  }
841
- function useLoad(load, deps) {
842
- const [data, setData] = useState(null);
843
- const [error, setError] = useState(null);
844
- useEffect(() => {
973
+ function useLoad(load2, deps) {
974
+ const [data, setData] = useState2(null);
975
+ const [error, setError] = useState2(null);
976
+ useEffect2(() => {
845
977
  let alive = true;
846
978
  setData(null);
847
979
  setError(null);
848
- load().then((d) => alive && setData(d)).catch((e) => alive && setError(e instanceof YouAskedError ? e.code : "network"));
980
+ load2().then((d) => alive && setData(d)).catch((e) => alive && setError(e instanceof YouAskedError ? e.code : "network"));
849
981
  return () => {
850
982
  alive = false;
851
983
  };
852
984
  }, deps);
853
985
  return { data, error, setData };
854
986
  }
855
- function errorText(code) {
987
+ function errorText(code, detail) {
988
+ if (detail) return detail;
856
989
  switch (code) {
857
990
  case "signature_required":
858
991
  case "bad_signature":
859
992
  return "We couldn't confirm who you are. Refresh the page and try again.";
860
993
  case "slow_down":
861
994
  return "That's a lot of requests in an hour. Try again a bit later.";
995
+ case "looks_like_spam":
996
+ return "That looks like spam, so we didn't post it. Reword it and try again.";
862
997
  case "title_too_short":
863
998
  return "Add a few more words so others know what you mean.";
864
999
  case "network":
@@ -882,9 +1017,9 @@ function ago(t) {
882
1017
  return new Date(t).toLocaleDateString(void 0, { month: "short", day: "numeric" });
883
1018
  }
884
1019
  function AppAvatar({ icon, size = 36 }) {
885
- const [failed, setFailed] = useState(false);
1020
+ const [failed, setFailed] = useState2(false);
886
1021
  if (!icon || failed) return null;
887
- return /* @__PURE__ */ jsx2(
1022
+ return /* @__PURE__ */ jsx3(
888
1023
  "img",
889
1024
  {
890
1025
  className: "ya-avatar",
@@ -905,31 +1040,31 @@ function Header({
905
1040
  onClose,
906
1041
  right
907
1042
  }) {
908
- return /* @__PURE__ */ jsxs2("div", { className: onBack ? "ya-head ya-head-back" : "ya-head", children: [
909
- onBack && /* @__PURE__ */ jsx2("button", { type: "button", className: "ya-icon-btn", onClick: onBack, "aria-label": "Back", children: /* @__PURE__ */ jsx2(IconBack, {}) }),
1043
+ return /* @__PURE__ */ jsxs3("div", { className: onBack ? "ya-head ya-head-back" : "ya-head", children: [
1044
+ onBack && /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-icon-btn", onClick: onBack, "aria-label": "Back", children: /* @__PURE__ */ jsx3(IconBack, {}) }),
910
1045
  leading,
911
- /* @__PURE__ */ jsxs2("div", { className: "ya-head-text", children: [
912
- /* @__PURE__ */ jsx2("h2", { className: "ya-h", children: title }),
913
- subtitle && /* @__PURE__ */ jsx2("p", { className: "ya-sub", children: subtitle })
1046
+ /* @__PURE__ */ jsxs3("div", { className: "ya-head-text", children: [
1047
+ /* @__PURE__ */ jsx3("h2", { className: "ya-h", children: title }),
1048
+ subtitle && /* @__PURE__ */ jsx3("p", { className: "ya-sub", children: subtitle })
914
1049
  ] }),
915
1050
  right,
916
- onClose && /* @__PURE__ */ jsx2("button", { type: "button", className: "ya-icon-btn", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx2(IconClose, {}) })
1051
+ onClose && /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-icon-btn", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx3(IconClose, {}) })
917
1052
  ] });
918
1053
  }
919
1054
 
920
1055
  // src/views.tsx
921
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
922
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1056
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1057
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
923
1058
  function Navigator({ route, setRoute, onClose }) {
924
1059
  switch (route.view) {
925
1060
  case "home":
926
- return /* @__PURE__ */ jsx3(Home, { route, setRoute, onClose }, `${route.draft ?? ""}${route.focus ? "f" : ""}${route.topic ?? ""}`);
1061
+ return /* @__PURE__ */ jsx4(Home, { route, setRoute, onClose }, `${route.draft ?? ""}${route.focus ? "f" : ""}${route.topic ?? ""}`);
927
1062
  case "board":
928
- return /* @__PURE__ */ jsx3(Board, { initialTab: route.tab, setRoute, onClose });
1063
+ return /* @__PURE__ */ jsx4(Board, { initialTab: route.tab, setRoute, onClose });
929
1064
  case "detail":
930
- return /* @__PURE__ */ jsx3(Detail, { id: route.id, setRoute, onClose }, route.id);
1065
+ return /* @__PURE__ */ jsx4(Detail, { id: route.id, from: route.from, setRoute, onClose }, route.id);
931
1066
  case "mine":
932
- return /* @__PURE__ */ jsx3(Mine, { setRoute, onClose });
1067
+ return /* @__PURE__ */ jsx4(Mine, { setRoute, onClose });
933
1068
  }
934
1069
  }
935
1070
  function splitMessage(text) {
@@ -941,23 +1076,23 @@ function splitMessage(text) {
941
1076
  return { title, body: t };
942
1077
  }
943
1078
  function ReplyBubble({ notice, appName, onOpen }) {
944
- return /* @__PURE__ */ jsxs3("div", { className: "ya-bubble ya-bubble-in", children: [
945
- /* @__PURE__ */ jsxs3("span", { className: "ya-bubble-kicker", children: [
1079
+ return /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in", children: [
1080
+ /* @__PURE__ */ jsxs4("span", { className: "ya-bubble-kicker", children: [
946
1081
  appName,
947
1082
  " replied about ",
948
1083
  notice.title
949
1084
  ] }),
950
- /* @__PURE__ */ jsx3("span", { children: notice.reply }),
951
- /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-bubble-link", onClick: onOpen, children: "Reply" })
1085
+ /* @__PURE__ */ jsx4("span", { children: notice.reply }),
1086
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-bubble-link", onClick: onOpen, children: "Reply" })
952
1087
  ] });
953
1088
  }
954
1089
  var MAX_SHOTS = 3;
955
1090
  function Confetti() {
956
1091
  const colors = ["var(--_accent)", "var(--_green)", "var(--_amber)", "#ff7eb3"];
957
- return /* @__PURE__ */ jsx3("span", { className: "ya-confetti", "aria-hidden": "true", children: Array.from({ length: 14 }, (_, i) => {
1092
+ return /* @__PURE__ */ jsx4("span", { className: "ya-confetti", "aria-hidden": "true", children: Array.from({ length: 14 }, (_, i) => {
958
1093
  const angle = Math.PI * (0.15 + i / 13 * 0.7) % Math.PI;
959
1094
  const dist = 38 + i % 4 * 14;
960
- return /* @__PURE__ */ jsx3(
1095
+ return /* @__PURE__ */ jsx4(
961
1096
  "i",
962
1097
  {
963
1098
  style: {
@@ -976,33 +1111,33 @@ function Home({ route, setRoute, onClose }) {
976
1111
  const { client, config, appName, appIcon, greeting, markSeen, whatsNew, dismissWhatsNew, pointAtIt } = useInternal();
977
1112
  const top = useLoad(() => client.list("top", "active"), [client]);
978
1113
  const shipped = useLoad(() => client.list("new", "shipped"), [client]);
979
- const [text, setText] = useState2(route.draft ?? "");
980
- const [thread, setThread] = useState2([]);
981
- const [similar, setSimilar] = useState2([]);
982
- const [notices] = useState2(() => config?.unseen ?? []);
983
- const [news] = useState2(() => whatsNew);
984
- const [shots, setShots] = useState2([]);
985
- const [shotError, setShotError] = useState2(null);
986
- const [credit, setCredit] = useState2(false);
987
- const urls = useRef2([]);
988
- const inputRef = useRef2(null);
989
- const bodyRef = useRef2(null);
1114
+ const [text, setText] = useState3(route.draft ?? "");
1115
+ const [thread, setThread] = useState3([]);
1116
+ const [similar, setSimilar] = useState3([]);
1117
+ const [notices] = useState3(() => config?.unseen ?? []);
1118
+ const [news] = useState3(() => whatsNew);
1119
+ const [shots, setShots] = useState3([]);
1120
+ const [shotError, setShotError] = useState3(null);
1121
+ const [credit, setCredit] = useState3(false);
1122
+ const urls = useRef3([]);
1123
+ const inputRef = useRef3(null);
1124
+ const bodyRef = useRef3(null);
990
1125
  const sending = thread.some((t) => t.kind === "typing");
991
- useEffect2(() => {
1126
+ useEffect3(() => {
992
1127
  if (notices.length) markSeen(notices.map((n) => n.id));
993
1128
  }, [notices, markSeen]);
994
- useEffect2(() => {
1129
+ useEffect3(() => {
995
1130
  if (news) dismissWhatsNew();
996
1131
  }, [news, dismissWhatsNew]);
997
1132
  const arrived = config?.unseen.filter((n) => n.kind === "reply" && !notices.some((o) => o.id === n.id)) ?? [];
998
1133
  const arrivedKey = arrived.map((n) => n.id).join(",");
999
- useEffect2(() => {
1134
+ useEffect3(() => {
1000
1135
  if (!arrived.length) return;
1001
1136
  setThread((th) => [...th, ...arrived.map((notice) => ({ kind: "reply", notice }))]);
1002
1137
  markSeen(arrived.map((n) => n.id));
1003
1138
  }, [arrivedKey]);
1004
- useEffect2(() => () => urls.current.forEach((u) => URL.revokeObjectURL(u)), []);
1005
- useEffect2(() => {
1139
+ useEffect3(() => () => urls.current.forEach((u) => URL.revokeObjectURL(u)), []);
1140
+ useEffect3(() => {
1006
1141
  if (!route.focus) return;
1007
1142
  const t = setTimeout(() => {
1008
1143
  const el = inputRef.current;
@@ -1012,16 +1147,16 @@ function Home({ route, setRoute, onClose }) {
1012
1147
  }, 60);
1013
1148
  return () => clearTimeout(t);
1014
1149
  }, [route.focus]);
1015
- useEffect2(() => {
1150
+ useEffect3(() => {
1016
1151
  const el = inputRef.current;
1017
1152
  if (!el) return;
1018
1153
  el.style.height = "";
1019
1154
  if (text.includes("\n") || el.scrollHeight > el.clientHeight + 2) el.style.height = `${Math.min(el.scrollHeight, 132)}px`;
1020
1155
  }, [text]);
1021
- useEffect2(() => {
1156
+ useEffect3(() => {
1022
1157
  if (thread.length) bodyRef.current?.scrollTo({ top: bodyRef.current.scrollHeight, behavior: "smooth" });
1023
1158
  }, [thread]);
1024
- useEffect2(() => {
1159
+ useEffect3(() => {
1025
1160
  const q = text.trim();
1026
1161
  if (q.length < 4) {
1027
1162
  setSimilar([]);
@@ -1074,7 +1209,7 @@ function Home({ route, setRoute, onClose }) {
1074
1209
  } catch (err) {
1075
1210
  setThread((th) => [
1076
1211
  ...th.filter((t) => t.kind !== "typing" && !(t.kind === "out" && t.text === message)),
1077
- { kind: "error", text: errorText(err instanceof YouAskedError ? err.code : "network") }
1212
+ { kind: "error", text: errorText(err instanceof YouAskedError ? err.code : "network", err instanceof YouAskedError ? err.detail : void 0) }
1078
1213
  ]);
1079
1214
  setText(message);
1080
1215
  setShots(attached);
@@ -1082,106 +1217,106 @@ function Home({ route, setRoute, onClose }) {
1082
1217
  }
1083
1218
  };
1084
1219
  const chatting = thread.length > 0;
1085
- const openRequest = (id) => setRoute({ view: "detail", id });
1220
+ const openRequest = (id) => setRoute({ view: "detail", id, from: "home" });
1086
1221
  const asking = route.topic && route.placeholder ? route.topic : null;
1087
1222
  const placeholder = route.placeholder ?? "Ask for a feature\u2026";
1088
1223
  const yours = news?.items.find((i) => i.mine);
1089
1224
  const listed = new Set(news?.items.map((i) => i.id));
1090
1225
  const shownNotices = notices.filter((n) => !(n.kind === "shipped" && listed.has(n.requestId)));
1091
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1092
- /* @__PURE__ */ jsx3(
1226
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1227
+ /* @__PURE__ */ jsx4(
1093
1228
  Header,
1094
1229
  {
1095
- leading: /* @__PURE__ */ jsx3(AppAvatar, { icon: appIcon }),
1230
+ leading: /* @__PURE__ */ jsx4(AppAvatar, { icon: appIcon }),
1096
1231
  title: appName,
1097
1232
  subtitle: "Ideas, votes and what's new",
1098
1233
  onClose
1099
1234
  }
1100
1235
  ),
1101
- /* @__PURE__ */ jsxs3("div", { className: "ya-body ya-chat", ref: bodyRef, children: [
1102
- /* @__PURE__ */ jsx3("div", { className: "ya-bubble ya-bubble-in", children: asking ? `Let's talk about ${asking}. What's missing, or what bugs you?` : greeting ?? `Hey! What would make ${appName} better for you?` }),
1103
- news && /* @__PURE__ */ jsxs3("div", { className: yours ? "ya-bubble ya-bubble-in ya-bubble-win ya-bubble-news" : "ya-bubble ya-bubble-in ya-bubble-news", children: [
1104
- yours && /* @__PURE__ */ jsx3(Confetti, {}),
1105
- /* @__PURE__ */ jsx3("span", { className: "ya-bubble-kicker", children: whatsNewHeading(news.total) }),
1106
- /* @__PURE__ */ jsx3("ul", { className: "ya-news", children: news.items.map((i) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3("button", { type: "button", onClick: () => openRequest(i.id), children: [
1107
- /* @__PURE__ */ jsx3("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx3(IconCheck, {}) }),
1108
- /* @__PURE__ */ jsxs3("span", { className: "ya-news-text", children: [
1109
- /* @__PURE__ */ jsx3("span", { className: "ya-news-title", children: i.title }),
1110
- i.mine && /* @__PURE__ */ jsx3("span", { className: "ya-news-you", children: "You asked for this" })
1236
+ /* @__PURE__ */ jsxs4("div", { className: "ya-body ya-chat", ref: bodyRef, children: [
1237
+ /* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-in", children: asking ? `Let's talk about ${asking}. What's missing, or what bugs you?` : greeting ?? `Hey! What would make ${appName} better for you?` }),
1238
+ news && /* @__PURE__ */ jsxs4("div", { className: yours ? "ya-bubble ya-bubble-in ya-bubble-win ya-bubble-news" : "ya-bubble ya-bubble-in ya-bubble-news", children: [
1239
+ yours && /* @__PURE__ */ jsx4(Confetti, {}),
1240
+ /* @__PURE__ */ jsx4("span", { className: "ya-bubble-kicker", children: whatsNewHeading(news.total) }),
1241
+ /* @__PURE__ */ jsx4("ul", { className: "ya-news", children: news.items.map((i) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4("button", { type: "button", onClick: () => openRequest(i.id), children: [
1242
+ /* @__PURE__ */ jsx4("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx4(IconCheck, {}) }),
1243
+ /* @__PURE__ */ jsxs4("span", { className: "ya-news-text", children: [
1244
+ /* @__PURE__ */ jsx4("span", { className: "ya-news-title", children: i.title }),
1245
+ i.mine && /* @__PURE__ */ jsx4("span", { className: "ya-news-you", children: "You asked for this" })
1111
1246
  ] }),
1112
- /* @__PURE__ */ jsx3(IconChevron, {})
1247
+ /* @__PURE__ */ jsx4(IconChevron, {})
1113
1248
  ] }) }, i.id)) }),
1114
- yours && /* @__PURE__ */ jsx3("span", { children: "That one was yours. Thanks for asking." }),
1115
- news.total > news.items.length && /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-bubble-link", onClick: () => setRoute({ view: "board", tab: "shipped" }), children: [
1249
+ yours && /* @__PURE__ */ jsx4("span", { children: "That one was yours. Thanks for asking." }),
1250
+ news.total > news.items.length && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-bubble-link", onClick: () => setRoute({ view: "board", tab: "shipped" }), children: [
1116
1251
  "See all ",
1117
1252
  news.total
1118
1253
  ] })
1119
1254
  ] }),
1120
1255
  shownNotices.map(
1121
- (n) => n.kind === "reply" ? /* @__PURE__ */ jsx3(ReplyBubble, { notice: n, appName, onOpen: () => openRequest(n.requestId) }, n.id) : /* @__PURE__ */ jsxs3("div", { className: "ya-bubble ya-bubble-in ya-bubble-win", children: [
1122
- /* @__PURE__ */ jsxs3("span", { className: "ya-live", children: [
1123
- /* @__PURE__ */ jsx3(IconCheck, {}),
1256
+ (n) => n.kind === "reply" ? /* @__PURE__ */ jsx4(ReplyBubble, { notice: n, appName, onOpen: () => openRequest(n.requestId) }, n.id) : /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in ya-bubble-win", children: [
1257
+ /* @__PURE__ */ jsxs4("span", { className: "ya-live", children: [
1258
+ /* @__PURE__ */ jsx4(IconCheck, {}),
1124
1259
  "It's live"
1125
1260
  ] }),
1126
- /* @__PURE__ */ jsxs3("span", { children: [
1261
+ /* @__PURE__ */ jsxs4("span", { children: [
1127
1262
  "You asked for ",
1128
- /* @__PURE__ */ jsx3("b", { children: n.title }),
1263
+ /* @__PURE__ */ jsx4("b", { children: n.title }),
1129
1264
  ". ",
1130
1265
  n.shipNote ?? "Thanks for asking."
1131
1266
  ] }),
1132
- /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-bubble-link", onClick: () => openRequest(n.requestId), children: "See it" })
1267
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-bubble-link", onClick: () => openRequest(n.requestId), children: "See it" })
1133
1268
  ] }, n.id)
1134
1269
  ),
1135
- !chatting && /* @__PURE__ */ jsxs3(Fragment, { children: [
1136
- /* @__PURE__ */ jsx3(Section, { title: "Popular right now", action: "See all", onAction: () => setRoute({ view: "board" }), children: top.error ? /* @__PURE__ */ jsx3("p", { className: "ya-note", children: errorText(top.error) }) : !top.data ? /* @__PURE__ */ jsx3(Skeleton, { rows: 2 }) : top.data.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "ya-note", children: "No requests yet. Yours could be the first." }) : /* @__PURE__ */ jsx3("ul", { className: "ya-list", children: top.data.slice(0, 3).map((r) => /* @__PURE__ */ jsx3(RequestRow, { client, request: r, compact: true, onOpen: () => openRequest(r.id) }, r.id)) }) }),
1137
- shipped.data && shipped.data.length > 0 && /* @__PURE__ */ jsx3(Section, { title: "Recently shipped", action: "All", onAction: () => setRoute({ view: "board", tab: "shipped" }), children: /* @__PURE__ */ jsx3("ul", { className: "ya-shipped", children: shipped.data.slice(0, 3).map((r) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3("button", { type: "button", onClick: () => openRequest(r.id), children: [
1138
- /* @__PURE__ */ jsx3("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx3(IconCheck, {}) }),
1139
- /* @__PURE__ */ jsx3("span", { className: "ya-shipped-title", children: r.title }),
1140
- /* @__PURE__ */ jsx3("span", { className: "ya-shipped-when", children: r.shippedAt ? ago(r.shippedAt) : "" })
1270
+ !chatting && /* @__PURE__ */ jsxs4(Fragment, { children: [
1271
+ /* @__PURE__ */ jsx4(Section, { title: "Popular right now", action: "See all", onAction: () => setRoute({ view: "board" }), children: top.error ? /* @__PURE__ */ jsx4("p", { className: "ya-note", children: errorText(top.error) }) : !top.data ? /* @__PURE__ */ jsx4(Skeleton, { rows: 2 }) : top.data.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "ya-note", children: "No requests yet. Yours could be the first." }) : /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: top.data.slice(0, 3).map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, compact: true, onOpen: () => openRequest(r.id) }, r.id)) }) }),
1272
+ shipped.data && shipped.data.length > 0 && /* @__PURE__ */ jsx4(Section, { title: "Recently shipped", action: "All", onAction: () => setRoute({ view: "board", tab: "shipped" }), children: /* @__PURE__ */ jsx4("ul", { className: "ya-shipped", children: shipped.data.slice(0, 3).map((r) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsxs4("button", { type: "button", onClick: () => openRequest(r.id), children: [
1273
+ /* @__PURE__ */ jsx4("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx4(IconCheck, {}) }),
1274
+ /* @__PURE__ */ jsx4("span", { className: "ya-shipped-title", children: r.title }),
1275
+ /* @__PURE__ */ jsx4("span", { className: "ya-shipped-when", children: r.shippedAt ? ago(r.shippedAt) : "" })
1141
1276
  ] }) }, r.id)) }) })
1142
1277
  ] }),
1143
1278
  thread.map(
1144
- (t, i) => t.kind === "out" ? /* @__PURE__ */ jsxs3("div", { className: "ya-bubble ya-bubble-out", children: [
1145
- t.images?.map((src) => /* @__PURE__ */ jsx3("img", { className: "ya-bubble-img", src, alt: "Your screenshot" }, src)),
1279
+ (t, i) => t.kind === "out" ? /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-out", children: [
1280
+ t.images?.map((src) => /* @__PURE__ */ jsx4("img", { className: "ya-bubble-img", src, alt: "Your screenshot" }, src)),
1146
1281
  t.text
1147
- ] }, i) : t.kind === "typing" ? /* @__PURE__ */ jsxs3("div", { className: "ya-bubble ya-bubble-in ya-typing", "aria-label": "Posting", children: [
1148
- /* @__PURE__ */ jsx3("span", {}),
1149
- /* @__PURE__ */ jsx3("span", {}),
1150
- /* @__PURE__ */ jsx3("span", {})
1151
- ] }, i) : t.kind === "reply" ? /* @__PURE__ */ jsx3(ReplyBubble, { notice: t.notice, appName, onOpen: () => openRequest(t.notice.requestId) }, i) : t.kind === "posted" ? /* @__PURE__ */ jsxs3("div", { className: "ya-bubble ya-bubble-in", children: [
1152
- /* @__PURE__ */ jsxs3("span", { children: [
1282
+ ] }, i) : t.kind === "typing" ? /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in ya-typing", "aria-label": "Posting", children: [
1283
+ /* @__PURE__ */ jsx4("span", {}),
1284
+ /* @__PURE__ */ jsx4("span", {}),
1285
+ /* @__PURE__ */ jsx4("span", {})
1286
+ ] }, i) : t.kind === "reply" ? /* @__PURE__ */ jsx4(ReplyBubble, { notice: t.notice, appName, onOpen: () => openRequest(t.notice.requestId) }, i) : t.kind === "posted" ? /* @__PURE__ */ jsxs4("div", { className: "ya-bubble ya-bubble-in", children: [
1287
+ /* @__PURE__ */ jsxs4("span", { children: [
1153
1288
  "Got it. ",
1154
- /* @__PURE__ */ jsx3("b", { children: t.request.title }),
1289
+ /* @__PURE__ */ jsx4("b", { children: t.request.title }),
1155
1290
  " is on the board, and we'll tell you right here when it ships."
1156
1291
  ] }),
1157
- /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-bubble-link", onClick: () => openRequest(t.request.id), children: "View request" })
1158
- ] }, i) : /* @__PURE__ */ jsx3("div", { className: "ya-bubble ya-bubble-in ya-bubble-error", role: "alert", children: t.text }, i)
1292
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-bubble-link", onClick: () => openRequest(t.request.id), children: "View request" })
1293
+ ] }, i) : /* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-in ya-bubble-error", role: "alert", children: t.text }, i)
1159
1294
  ),
1160
- chatting && !sending && /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-chip", onClick: () => setRoute({ view: "board" }), children: "See what others asked for" })
1295
+ chatting && !sending && /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-chip", onClick: () => setRoute({ view: "board" }), children: "See what others asked for" })
1161
1296
  ] }),
1162
- /* @__PURE__ */ jsxs3("div", { className: "ya-composer-wrap", children: [
1163
- similar.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "ya-tray", "aria-live": "polite", children: [
1164
- /* @__PURE__ */ jsx3("p", { className: "ya-tray-h", children: "Sounds like one of these. Vote instead?" }),
1165
- /* @__PURE__ */ jsx3("ul", { className: "ya-list", children: similar.map((r) => /* @__PURE__ */ jsx3(RequestRow, { client, request: r, compact: true, onOpen: () => openRequest(r.id) }, r.id)) })
1297
+ /* @__PURE__ */ jsxs4("div", { className: "ya-composer-wrap", children: [
1298
+ similar.length > 0 && /* @__PURE__ */ jsxs4("div", { className: "ya-tray", "aria-live": "polite", children: [
1299
+ /* @__PURE__ */ jsx4("p", { className: "ya-tray-h", children: "Sounds like one of these. Vote instead?" }),
1300
+ /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: similar.map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, compact: true, onOpen: () => openRequest(r.id) }, r.id)) })
1166
1301
  ] }),
1167
- shots.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "ya-shots", "aria-label": "Screenshots", children: shots.map((s, i) => /* @__PURE__ */ jsxs3("li", { className: "ya-shot", children: [
1168
- /* @__PURE__ */ jsx3("img", { src: s.url, alt: "" }),
1169
- /* @__PURE__ */ jsxs3("span", { children: [
1302
+ shots.length > 0 && /* @__PURE__ */ jsx4("ul", { className: "ya-shots", "aria-label": "Screenshots", children: shots.map((s, i) => /* @__PURE__ */ jsxs4("li", { className: "ya-shot", children: [
1303
+ /* @__PURE__ */ jsx4("img", { src: s.url, alt: "" }),
1304
+ /* @__PURE__ */ jsxs4("span", { children: [
1170
1305
  "Screenshot",
1171
1306
  shots.length > 1 ? ` ${i + 1}` : ""
1172
1307
  ] }),
1173
- /* @__PURE__ */ jsx3(
1308
+ /* @__PURE__ */ jsx4(
1174
1309
  "button",
1175
1310
  {
1176
1311
  type: "button",
1177
1312
  className: "ya-shot-x",
1178
1313
  "aria-label": `Remove screenshot${shots.length > 1 ? ` ${i + 1}` : ""}`,
1179
1314
  onClick: () => setShots((all) => all.filter((x) => x !== s)),
1180
- children: /* @__PURE__ */ jsx3(IconClose, {})
1315
+ children: /* @__PURE__ */ jsx4(IconClose, {})
1181
1316
  }
1182
1317
  )
1183
1318
  ] }, s.url)) }),
1184
- /* @__PURE__ */ jsxs3(
1319
+ /* @__PURE__ */ jsxs4(
1185
1320
  "form",
1186
1321
  {
1187
1322
  className: "ya-composer",
@@ -1190,7 +1325,7 @@ function Home({ route, setRoute, onClose }) {
1190
1325
  send();
1191
1326
  },
1192
1327
  children: [
1193
- /* @__PURE__ */ jsx3(
1328
+ /* @__PURE__ */ jsx4(
1194
1329
  "textarea",
1195
1330
  {
1196
1331
  ref: inputRef,
@@ -1209,12 +1344,12 @@ function Home({ route, setRoute, onClose }) {
1209
1344
  }
1210
1345
  }
1211
1346
  ),
1212
- /* @__PURE__ */ jsx3("button", { type: "submit", className: "ya-send", disabled: text.trim().length < 3 || sending, "aria-label": "Send", children: /* @__PURE__ */ jsx3(IconSend, {}) })
1347
+ /* @__PURE__ */ jsx4("button", { type: "submit", className: "ya-send", disabled: text.trim().length < 3 || sending, "aria-label": "Send", children: /* @__PURE__ */ jsx4(IconSend, {}) })
1213
1348
  ]
1214
1349
  }
1215
1350
  ),
1216
- /* @__PURE__ */ jsxs3("div", { className: "ya-tools", children: [
1217
- /* @__PURE__ */ jsxs3(
1351
+ /* @__PURE__ */ jsxs4("div", { className: "ya-tools", children: [
1352
+ /* @__PURE__ */ jsxs4(
1218
1353
  "button",
1219
1354
  {
1220
1355
  type: "button",
@@ -1224,18 +1359,18 @@ function Home({ route, setRoute, onClose }) {
1224
1359
  onFocus: () => void import("./capture-FYZGL54F.js"),
1225
1360
  disabled: sending || shots.length >= MAX_SHOTS,
1226
1361
  children: [
1227
- /* @__PURE__ */ jsx3(IconFrame, {}),
1362
+ /* @__PURE__ */ jsx4(IconFrame, {}),
1228
1363
  "Show us"
1229
1364
  ]
1230
1365
  }
1231
1366
  ),
1232
- config?.canCredit && text.trim().length > 0 && /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-switch-row", role: "switch", "aria-checked": credit, onClick: () => setCredit((c) => !c), children: [
1367
+ config?.canCredit && text.trim().length > 0 && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-switch-row", role: "switch", "aria-checked": credit, onClick: () => setCredit((c) => !c), children: [
1233
1368
  "Credit me when it ships",
1234
- /* @__PURE__ */ jsx3("span", { className: "ya-switch", "aria-hidden": "true" })
1369
+ /* @__PURE__ */ jsx4("span", { className: "ya-switch", "aria-hidden": "true" })
1235
1370
  ] })
1236
1371
  ] }),
1237
- shotError && /* @__PURE__ */ jsx3("p", { className: "ya-note", role: "alert", children: shotError }),
1238
- config?.watermark && /* @__PURE__ */ jsx3("p", { className: "ya-mark", children: "Feedback by YouAsked" })
1372
+ shotError && /* @__PURE__ */ jsx4("p", { className: "ya-note", role: "alert", children: shotError }),
1373
+ config?.watermark && /* @__PURE__ */ jsx4("p", { className: "ya-mark", children: "Feedback by YouAsked" })
1239
1374
  ] })
1240
1375
  ] });
1241
1376
  }
@@ -1245,12 +1380,12 @@ function Section({
1245
1380
  onAction,
1246
1381
  children
1247
1382
  }) {
1248
- return /* @__PURE__ */ jsxs3("section", { className: "ya-section", children: [
1249
- /* @__PURE__ */ jsxs3("div", { className: "ya-section-head", children: [
1250
- /* @__PURE__ */ jsx3("h3", { children: title }),
1251
- action && /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-link", onClick: onAction, children: [
1383
+ return /* @__PURE__ */ jsxs4("section", { className: "ya-section", children: [
1384
+ /* @__PURE__ */ jsxs4("div", { className: "ya-section-head", children: [
1385
+ /* @__PURE__ */ jsx4("h3", { children: title }),
1386
+ action && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-link", onClick: onAction, children: [
1252
1387
  action,
1253
- /* @__PURE__ */ jsx3(IconChevron, {})
1388
+ /* @__PURE__ */ jsx4(IconChevron, {})
1254
1389
  ] })
1255
1390
  ] }),
1256
1391
  children
@@ -1267,36 +1402,36 @@ function Board({
1267
1402
  onClose
1268
1403
  }) {
1269
1404
  const { client, title } = useInternal();
1270
- const [tab, setTab] = useState2(TABS.find((t) => t.key === initialTab) ?? TABS[0]);
1405
+ const [tab, setTab] = useState3(TABS.find((t) => t.key === initialTab) ?? TABS[0]);
1271
1406
  const { data, error } = useLoad(() => client.list(tab.sort, tab.status), [client, tab.key]);
1272
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1273
- /* @__PURE__ */ jsx3(
1407
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1408
+ /* @__PURE__ */ jsx4(
1274
1409
  Header,
1275
1410
  {
1276
1411
  title,
1277
1412
  onBack: () => setRoute({ view: "home" }),
1278
1413
  onClose,
1279
- right: /* @__PURE__ */ jsx3("button", { type: "button", className: "ya-link", onClick: () => setRoute({ view: "mine" }), children: "Yours" })
1414
+ right: /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link", onClick: () => setRoute({ view: "mine" }), children: "Yours" })
1280
1415
  }
1281
1416
  ),
1282
- /* @__PURE__ */ jsx3("div", { className: "ya-tabs", role: "tablist", "aria-label": "Sort requests", children: TABS.map((t) => /* @__PURE__ */ jsx3("button", { type: "button", role: "tab", className: "ya-tab", "aria-selected": tab.key === t.key, onClick: () => setTab(t), children: t.label }, t.key)) }),
1283
- /* @__PURE__ */ jsx3("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx3("p", { className: "ya-empty", children: errorText(error) }) : !data ? /* @__PURE__ */ jsx3(Skeleton, {}) : data.length === 0 ? /* @__PURE__ */ jsxs3("div", { className: "ya-empty", children: [
1284
- /* @__PURE__ */ jsx3("b", { children: tab.key === "shipped" ? "Nothing shipped yet" : "No requests yet" }),
1417
+ /* @__PURE__ */ jsx4("div", { className: "ya-tabs", role: "tablist", "aria-label": "Sort requests", children: TABS.map((t) => /* @__PURE__ */ jsx4("button", { type: "button", role: "tab", className: "ya-tab", "aria-selected": tab.key === t.key, onClick: () => setTab(t), children: t.label }, t.key)) }),
1418
+ /* @__PURE__ */ jsx4("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx4("p", { className: "ya-empty", children: errorText(error) }) : !data ? /* @__PURE__ */ jsx4(Skeleton, {}) : data.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "ya-empty", children: [
1419
+ /* @__PURE__ */ jsx4("b", { children: tab.key === "shipped" ? "Nothing shipped yet" : "No requests yet" }),
1285
1420
  tab.key === "shipped" ? "Requests show up here once they're live." : "Tell us what you'd like next. Others can vote on it."
1286
- ] }) : /* @__PURE__ */ jsx3("ul", { className: "ya-list", children: data.map((r) => /* @__PURE__ */ jsx3(RequestRow, { client, request: r, onOpen: () => setRoute({ view: "detail", id: r.id }) }, r.id)) }) }),
1287
- /* @__PURE__ */ jsx3("div", { className: "ya-foot", children: /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-btn", onClick: () => setRoute({ view: "home", focus: true }), children: [
1288
- /* @__PURE__ */ jsx3(IconPlus, {}),
1421
+ ] }) : /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: data.map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, onOpen: () => setRoute({ view: "detail", id: r.id, from: "board" }) }, r.id)) }) }),
1422
+ /* @__PURE__ */ jsx4("div", { className: "ya-foot", children: /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-btn", onClick: () => setRoute({ view: "home", focus: true }), children: [
1423
+ /* @__PURE__ */ jsx4(IconPlus, {}),
1289
1424
  "Request a feature"
1290
1425
  ] }) })
1291
1426
  ] });
1292
1427
  }
1293
- function Detail({ id, setRoute, onClose }) {
1428
+ function Detail({ id, from, setRoute, onClose }) {
1294
1429
  const { client, config, markSeen, appName, appIcon } = useInternal();
1295
1430
  const { data, error, setData } = useLoad(() => client.get(id), [client, id]);
1296
1431
  const unseenHere = config?.unseen.filter((n) => n.requestId === id).map((n) => n.id).join(",") ?? "";
1297
- const loaded = useRef2(false);
1432
+ const loaded = useRef3(false);
1298
1433
  loaded.current = Boolean(data);
1299
- useEffect2(() => {
1434
+ useEffect3(() => {
1300
1435
  if (!unseenHere) return;
1301
1436
  markSeen(unseenHere.split(","));
1302
1437
  if (loaded.current) {
@@ -1304,12 +1439,27 @@ function Detail({ id, setRoute, onClose }) {
1304
1439
  });
1305
1440
  }
1306
1441
  }, [unseenHere, markSeen]);
1307
- const [reply, setReply] = useState2("");
1308
- const [sending, setSending] = useState2(false);
1309
- const [replyError, setReplyError] = useState2(null);
1310
- const [voted, setVoted] = useState2(null);
1311
- const [early, setEarly] = useState2(false);
1312
- useEffect2(() => setEarly(Boolean(data?.earlyAccess)), [data?.earlyAccess]);
1442
+ const [reply, setReply] = useState3("");
1443
+ const [sending, setSending] = useState3(false);
1444
+ const [replyError, setReplyError] = useState3(null);
1445
+ const [voted, setVoted] = useState3(null);
1446
+ const [early, setEarly] = useState3(false);
1447
+ useEffect3(() => setEarly(Boolean(data?.earlyAccess)), [data?.earlyAccess]);
1448
+ const [confirmDelete, setConfirmDelete] = useState3(false);
1449
+ const [deleting, setDeleting] = useState3(false);
1450
+ const [deleteError, setDeleteError] = useState3(null);
1451
+ const remove = async () => {
1452
+ if (!data?.request || deleting) return;
1453
+ setDeleting(true);
1454
+ setDeleteError(null);
1455
+ try {
1456
+ await client.remove(data.request.id);
1457
+ setRoute({ view: "mine" });
1458
+ } catch (err) {
1459
+ setDeleteError(errorText(err instanceof YouAskedError ? err.code : "network", err instanceof YouAskedError ? err.detail : void 0));
1460
+ setDeleting(false);
1461
+ }
1462
+ };
1313
1463
  const toggleEarly = async () => {
1314
1464
  if (!data?.request) return;
1315
1465
  const before = early;
@@ -1320,7 +1470,7 @@ function Detail({ id, setRoute, onClose }) {
1320
1470
  setEarly(before);
1321
1471
  }
1322
1472
  };
1323
- useEffect2(() => {
1473
+ useEffect3(() => {
1324
1474
  if (data?.redirect) setRoute({ view: "detail", id: data.redirect });
1325
1475
  }, [data?.redirect, setRoute]);
1326
1476
  const request = data?.request;
@@ -1335,51 +1485,63 @@ function Detail({ id, setRoute, onClose }) {
1335
1485
  setData({ ...data, comments: [...comments, c], request: { ...request, comments: request.comments + 1 } });
1336
1486
  setReply("");
1337
1487
  } catch (err) {
1338
- setReplyError(errorText(err instanceof YouAskedError ? err.code : "network"));
1488
+ setReplyError(errorText(err instanceof YouAskedError ? err.code : "network", err instanceof YouAskedError ? err.detail : void 0));
1339
1489
  } finally {
1340
1490
  setSending(false);
1341
1491
  }
1342
1492
  };
1343
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1344
- /* @__PURE__ */ jsx3(Header, { title: "", onBack: () => setRoute({ view: "board" }), onClose }),
1345
- /* @__PURE__ */ jsx3("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx3("p", { className: "ya-empty", children: error === "not_found" ? "This request was removed." : errorText(error) }) : !request ? /* @__PURE__ */ jsx3(Skeleton, {}) : /* @__PURE__ */ jsxs3(Fragment, { children: [
1346
- /* @__PURE__ */ jsxs3("div", { className: "ya-detail-meta", children: [
1347
- request.status !== "open" && /* @__PURE__ */ jsx3(StatusPill, { status: request.status }),
1348
- /* @__PURE__ */ jsxs3("span", { className: "ya-ref", children: [
1493
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1494
+ /* @__PURE__ */ jsx4(Header, { title: "", onBack: () => setRoute({ view: from ?? "board" }), onClose }),
1495
+ /* @__PURE__ */ jsx4("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx4("p", { className: "ya-empty", children: error === "not_found" ? "This request was removed." : errorText(error) }) : !request ? /* @__PURE__ */ jsx4(Skeleton, {}) : /* @__PURE__ */ jsxs4(Fragment, { children: [
1496
+ /* @__PURE__ */ jsxs4("div", { className: "ya-detail-meta", children: [
1497
+ request.status !== "open" && /* @__PURE__ */ jsx4(StatusPill, { status: request.status }),
1498
+ /* @__PURE__ */ jsxs4("span", { className: "ya-ref", children: [
1349
1499
  "YA-",
1350
1500
  request.number
1351
1501
  ] })
1352
1502
  ] }),
1353
- /* @__PURE__ */ jsx3("h3", { className: "ya-detail-title", children: request.title }),
1354
- request.body && request.body !== request.title && /* @__PURE__ */ jsx3("p", { className: "ya-text", children: request.body }),
1355
- data?.attachments && data.attachments.length > 0 && /* @__PURE__ */ jsx3("div", { className: "ya-attachments", children: data.attachments.map((a) => /* @__PURE__ */ jsx3("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx3("img", { src: a.url, alt: "Screenshot attached to this request", loading: "lazy" }) }, a.id)) }),
1356
- /* @__PURE__ */ jsx3(BigVote, { client, request, onChange: setVoted }),
1357
- (request.status === "planned" || request.status === "in_progress") && (voted ?? request.voted) && /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-early", role: "switch", "aria-checked": early, onClick: toggleEarly, children: [
1358
- /* @__PURE__ */ jsx3("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(IconSpark, {}) }),
1359
- /* @__PURE__ */ jsxs3("span", { className: "ya-menurow-text", children: [
1360
- /* @__PURE__ */ jsx3("span", { className: "ya-menurow-label", children: "Try it early" }),
1361
- /* @__PURE__ */ jsx3("span", { className: "ya-menurow-sub", children: early ? "You're on the list. We'll let you in first." : "Get access before everyone else" })
1503
+ /* @__PURE__ */ jsx4("h3", { className: "ya-detail-title", children: request.title }),
1504
+ request.body && request.body !== request.title && /* @__PURE__ */ jsx4("p", { className: "ya-text", children: request.body }),
1505
+ data?.attachments && data.attachments.length > 0 && /* @__PURE__ */ jsx4("div", { className: "ya-attachments", children: data.attachments.map((a) => /* @__PURE__ */ jsx4("a", { href: a.url, target: "_blank", rel: "noopener noreferrer", children: /* @__PURE__ */ jsx4("img", { src: a.url, alt: "Screenshot attached to this request", loading: "lazy" }) }, a.id)) }),
1506
+ /* @__PURE__ */ jsx4(BigVote, { client, request, onChange: setVoted }),
1507
+ (request.status === "planned" || request.status === "in_progress") && (voted ?? request.voted) && /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-early", role: "switch", "aria-checked": early, onClick: toggleEarly, children: [
1508
+ /* @__PURE__ */ jsx4("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx4(IconSpark, {}) }),
1509
+ /* @__PURE__ */ jsxs4("span", { className: "ya-menurow-text", children: [
1510
+ /* @__PURE__ */ jsx4("span", { className: "ya-menurow-label", children: "Try it early" }),
1511
+ /* @__PURE__ */ jsx4("span", { className: "ya-menurow-sub", children: early ? "You're on the list. We'll let you in first." : "Get access before everyone else" })
1362
1512
  ] }),
1363
- /* @__PURE__ */ jsx3("span", { className: "ya-switch", "aria-hidden": "true" })
1513
+ /* @__PURE__ */ jsx4("span", { className: "ya-switch", "aria-hidden": "true" })
1364
1514
  ] }),
1365
- request.mine && request.status !== "shipped" && /* @__PURE__ */ jsx3("p", { className: "ya-note", children: "You asked for this. We'll tell you here when it ships." }),
1366
- request.status === "shipped" && /* @__PURE__ */ jsxs3("div", { className: "ya-callout", "data-tone": "success", children: [
1367
- /* @__PURE__ */ jsx3(IconCheck, {}),
1368
- /* @__PURE__ */ jsxs3("span", { children: [
1369
- /* @__PURE__ */ jsx3("b", { children: "It's live." }),
1515
+ request.source && /* @__PURE__ */ jsxs4("p", { className: "ya-note", children: [
1516
+ "Also asked",
1517
+ " ",
1518
+ request.sourceUrl ? /* @__PURE__ */ jsx4("a", { href: request.sourceUrl, target: "_blank", rel: "noopener noreferrer", children: request.source === "reddit" ? "on Reddit" : "in App Store reviews" }) : request.source === "reddit" ? "on Reddit" : "in App Store reviews",
1519
+ "."
1520
+ ] }),
1521
+ request.mine && request.status !== "shipped" && /* @__PURE__ */ jsx4("p", { className: "ya-note", children: "You asked for this. We'll tell you here when it ships." }),
1522
+ data?.canDelete && (confirmDelete ? /* @__PURE__ */ jsxs4("p", { className: "ya-note ya-delete-row", children: [
1523
+ "Delete this request? This can't be undone.",
1524
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link ya-link-danger", onClick: remove, disabled: deleting, children: deleting ? "Deleting\u2026" : "Delete" }),
1525
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link", onClick: () => setConfirmDelete(false), disabled: deleting, children: "Keep it" })
1526
+ ] }) : /* @__PURE__ */ jsx4("p", { className: "ya-note ya-delete-row", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-link ya-link-quiet", onClick: () => setConfirmDelete(true), children: "Delete my request" }) })),
1527
+ deleteError && /* @__PURE__ */ jsx4("p", { className: "ya-note", role: "alert", children: deleteError }),
1528
+ request.status === "shipped" && /* @__PURE__ */ jsxs4("div", { className: "ya-callout", "data-tone": "success", children: [
1529
+ /* @__PURE__ */ jsx4(IconCheck, {}),
1530
+ /* @__PURE__ */ jsxs4("span", { children: [
1531
+ /* @__PURE__ */ jsx4("b", { children: "It's live." }),
1370
1532
  request.shipNote ? ` ${request.shipNote}` : " Thanks for asking.",
1371
- request.creditName && /* @__PURE__ */ jsxs3("span", { className: "ya-credit", children: [
1533
+ request.creditName && /* @__PURE__ */ jsxs4("span", { className: "ya-credit", children: [
1372
1534
  "Thanks to ",
1373
1535
  request.creditName,
1374
1536
  " for asking."
1375
1537
  ] })
1376
1538
  ] })
1377
1539
  ] }),
1378
- /* @__PURE__ */ jsx3("p", { className: "ya-section-h", children: "Conversation" }),
1379
- comments.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "ya-text ya-text-muted", children: "No messages yet. Add details or ask a question below." }) : /* @__PURE__ */ jsx3("div", { className: "ya-thread", children: comments.map((c) => /* @__PURE__ */ jsx3(Message, { comment: c, appName, appIcon }, c.id)) })
1540
+ /* @__PURE__ */ jsx4("p", { className: "ya-section-h", children: "Conversation" }),
1541
+ comments.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "ya-text ya-text-muted", children: "No messages yet. Add details or ask a question below." }) : /* @__PURE__ */ jsx4("div", { className: "ya-thread", children: comments.map((c) => /* @__PURE__ */ jsx4(Message, { comment: c, appName, appIcon }, c.id)) })
1380
1542
  ] }) }),
1381
- request && /* @__PURE__ */ jsxs3("form", { className: "ya-composer ya-composer-bar", onSubmit: send, children: [
1382
- /* @__PURE__ */ jsx3(
1543
+ request && /* @__PURE__ */ jsxs4("form", { className: "ya-composer ya-composer-bar", onSubmit: send, children: [
1544
+ /* @__PURE__ */ jsx4(
1383
1545
  "textarea",
1384
1546
  {
1385
1547
  className: "ya-composer-input",
@@ -1397,50 +1559,51 @@ function Detail({ id, setRoute, onClose }) {
1397
1559
  }
1398
1560
  }
1399
1561
  ),
1400
- /* @__PURE__ */ jsx3("button", { type: "submit", className: "ya-send", disabled: !reply.trim() || sending, "aria-label": "Send comment", children: /* @__PURE__ */ jsx3(IconSend, {}) }),
1401
- replyError && /* @__PURE__ */ jsx3("span", { className: "ya-sr", role: "alert", children: replyError })
1562
+ /* @__PURE__ */ jsx4("button", { type: "submit", className: "ya-send", disabled: !reply.trim() || sending, "aria-label": "Send comment", children: /* @__PURE__ */ jsx4(IconSend, {}) }),
1563
+ replyError && /* @__PURE__ */ jsx4("span", { className: "ya-sr", role: "alert", children: replyError })
1402
1564
  ] })
1403
1565
  ] });
1404
1566
  }
1405
1567
  function Message({ comment, appName, appIcon }) {
1406
- const when = /* @__PURE__ */ jsx3("time", { dateTime: new Date(comment.createdAt).toISOString(), children: ago(comment.createdAt) });
1568
+ const when = /* @__PURE__ */ jsx4("time", { dateTime: new Date(comment.createdAt).toISOString(), children: ago(comment.createdAt) });
1407
1569
  if (comment.mine) {
1408
- return /* @__PURE__ */ jsxs3("div", { className: "ya-msg ya-msg-out", children: [
1409
- /* @__PURE__ */ jsx3("div", { className: "ya-bubble ya-bubble-out", children: comment.body }),
1410
- /* @__PURE__ */ jsx3("span", { className: "ya-msg-meta", children: when })
1570
+ return /* @__PURE__ */ jsxs4("div", { className: "ya-msg ya-msg-out", children: [
1571
+ /* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-out", children: comment.body }),
1572
+ /* @__PURE__ */ jsx4("span", { className: "ya-msg-meta", children: when })
1411
1573
  ] });
1412
1574
  }
1413
- return /* @__PURE__ */ jsxs3("div", { className: "ya-msg", children: [
1414
- comment.team && appIcon ? /* @__PURE__ */ jsx3(AppAvatar, { icon: appIcon, size: 26 }) : null,
1415
- /* @__PURE__ */ jsxs3("div", { className: "ya-msg-body", children: [
1416
- /* @__PURE__ */ jsxs3("span", { className: "ya-msg-meta", children: [
1417
- /* @__PURE__ */ jsx3("b", { children: comment.team ? appName : comment.author || "Someone" }),
1418
- comment.team && /* @__PURE__ */ jsx3("span", { className: "ya-team", children: "Team" }),
1575
+ return /* @__PURE__ */ jsxs4("div", { className: "ya-msg", children: [
1576
+ comment.team && appIcon ? /* @__PURE__ */ jsx4(AppAvatar, { icon: appIcon, size: 26 }) : null,
1577
+ /* @__PURE__ */ jsxs4("div", { className: "ya-msg-body", children: [
1578
+ /* @__PURE__ */ jsxs4("span", { className: "ya-msg-meta", children: [
1579
+ /* @__PURE__ */ jsx4("b", { children: comment.team ? appName : comment.author || "Someone" }),
1580
+ comment.team && /* @__PURE__ */ jsx4("span", { className: "ya-team", children: "Team" }),
1419
1581
  when
1420
1582
  ] }),
1421
- /* @__PURE__ */ jsx3("div", { className: "ya-bubble ya-bubble-in", children: comment.body })
1583
+ /* @__PURE__ */ jsx4("div", { className: "ya-bubble ya-bubble-in", children: comment.body })
1422
1584
  ] })
1423
1585
  ] });
1424
1586
  }
1425
1587
  function Mine({ setRoute, onClose }) {
1426
1588
  const { client } = useInternal();
1427
1589
  const { data, error } = useLoad(() => client.mine(), [client]);
1428
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
1429
- /* @__PURE__ */ jsx3(Header, { title: "Your requests", onBack: () => setRoute({ view: "board" }), onClose }),
1430
- /* @__PURE__ */ jsx3("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx3("p", { className: "ya-empty", children: errorText(error) }) : !data ? /* @__PURE__ */ jsx3(Skeleton, {}) : data.length === 0 ? /* @__PURE__ */ jsxs3("div", { className: "ya-empty", children: [
1431
- /* @__PURE__ */ jsx3("b", { children: "Nothing yet" }),
1590
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1591
+ /* @__PURE__ */ jsx4(Header, { title: "Your requests", onBack: () => setRoute({ view: "board" }), onClose }),
1592
+ /* @__PURE__ */ jsx4("div", { className: "ya-body", children: error ? /* @__PURE__ */ jsx4("p", { className: "ya-empty", children: errorText(error) }) : !data ? /* @__PURE__ */ jsx4(Skeleton, {}) : data.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "ya-empty", children: [
1593
+ /* @__PURE__ */ jsx4("b", { children: "Nothing yet" }),
1432
1594
  "Requests you post or vote on show up here, with their status."
1433
- ] }) : /* @__PURE__ */ jsx3("ul", { className: "ya-list", children: data.map((r) => /* @__PURE__ */ jsx3(RequestRow, { client, request: r, onOpen: () => setRoute({ view: "detail", id: r.id }) }, r.id)) }) }),
1434
- /* @__PURE__ */ jsx3("div", { className: "ya-foot", children: /* @__PURE__ */ jsxs3("button", { type: "button", className: "ya-btn", onClick: () => setRoute({ view: "home", focus: true }), children: [
1435
- /* @__PURE__ */ jsx3(IconPlus, {}),
1595
+ ] }) : /* @__PURE__ */ jsx4("ul", { className: "ya-list", children: data.map((r) => /* @__PURE__ */ jsx4(RequestRow, { client, request: r, onOpen: () => setRoute({ view: "detail", id: r.id, from: "mine" }) }, r.id)) }) }),
1596
+ /* @__PURE__ */ jsx4("div", { className: "ya-foot", children: /* @__PURE__ */ jsxs4("button", { type: "button", className: "ya-btn", onClick: () => setRoute({ view: "home", focus: true }), children: [
1597
+ /* @__PURE__ */ jsx4(IconPlus, {}),
1436
1598
  "Request a feature"
1437
1599
  ] }) })
1438
1600
  ] });
1439
1601
  }
1440
1602
 
1441
1603
  // src/YouAsked.tsx
1442
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1443
- var DEFAULT_API_URL = "https://youasked-api.adenteo.workers.dev";
1604
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1605
+ var warnedTestMode = false;
1606
+ var DEFAULT_API_URL = "https://api.youasked.dev";
1444
1607
  function lastWhatsNew(key) {
1445
1608
  try {
1446
1609
  return key ? Number(localStorage.getItem(key)) || 0 : 0;
@@ -1455,6 +1618,7 @@ function YouAsked({
1455
1618
  appKey = "",
1456
1619
  demo = false,
1457
1620
  cards = true,
1621
+ nudge: nudgeProp,
1458
1622
  user,
1459
1623
  signature,
1460
1624
  ready = true,
@@ -1478,25 +1642,36 @@ function YouAsked({
1478
1642
  [demo, appName, appKey, apiUrl, userId, userEmail, userName, signature]
1479
1643
  );
1480
1644
  const whatsNewKey = demo ? null : `youasked:whats-new:${appKey}:${userId ?? "anon"}`;
1481
- const [mounted, setMounted] = useState3(false);
1482
- const [route, setRoute] = useState3(null);
1483
- const [config, setConfig] = useState3(null);
1484
- const [whatsNew, setWhatsNew] = useState3(null);
1485
- const [whatsNewToast, setWhatsNewToast] = useState3(false);
1486
- const [capture, setCapture] = useState3(null);
1645
+ const [mounted, setMounted] = useState4(false);
1646
+ const [route, setRoute] = useState4(null);
1647
+ const [config, setConfig] = useState4(null);
1648
+ const [testMode, setTestMode] = useState4(false);
1649
+ const [whatsNew, setWhatsNew] = useState4(null);
1650
+ const [whatsNewToast, setWhatsNewToast] = useState4(false);
1651
+ const [capture, setCapture] = useState4(null);
1487
1652
  const tokens = useTokens(appearance, mounted);
1488
- const openerRef = useRef3(null);
1489
- const [detectedIcon, setDetectedIcon] = useState3(null);
1490
- useEffect3(() => {
1653
+ const openerRef = useRef4(null);
1654
+ const [detectedIcon, setDetectedIcon] = useState4(null);
1655
+ useEffect4(() => {
1491
1656
  if (appIcon === void 0) setDetectedIcon(findPageIcon());
1492
1657
  }, [appIcon]);
1493
- useEffect3(() => {
1658
+ useEffect4(() => {
1494
1659
  injectStyles();
1495
1660
  setMounted(true);
1496
1661
  }, []);
1497
- useEffect3(() => {
1662
+ const [refresh, setRefresh] = useState4(0);
1663
+ const lastLoad = useRef4(0);
1664
+ useEffect4(() => {
1665
+ const onVisible = () => {
1666
+ if (document.visibilityState === "visible" && Date.now() - lastLoad.current > 6e4) setRefresh((n) => n + 1);
1667
+ };
1668
+ document.addEventListener("visibilitychange", onVisible);
1669
+ return () => document.removeEventListener("visibilitychange", onVisible);
1670
+ }, []);
1671
+ useEffect4(() => {
1498
1672
  if (!ready) return;
1499
1673
  let alive = true;
1674
+ lastLoad.current = Date.now();
1500
1675
  client.config().then((c) => {
1501
1676
  if (!alive) return;
1502
1677
  const seenAt = lastWhatsNew(whatsNewKey);
@@ -1505,19 +1680,25 @@ function YouAsked({
1505
1680
  setConfig(c);
1506
1681
  setWhatsNew(news);
1507
1682
  setWhatsNewToast(Boolean(news) && c.unseen.filter((n) => !coveredBy(news, n)).length === 0);
1508
- }).catch(() => {
1683
+ }).catch((err) => {
1684
+ if (!alive || !(err instanceof YouAskedError) || err.code !== "test_mode") return;
1685
+ setTestMode(true);
1686
+ if (!warnedTestMode) {
1687
+ warnedTestMode = true;
1688
+ console.warn(`[youasked] ${err.detail ?? "This app is in test mode. It works on localhost until it's on a paid plan."}`);
1689
+ }
1509
1690
  });
1510
1691
  return () => {
1511
1692
  alive = false;
1512
1693
  };
1513
- }, [client, ready]);
1514
- useEffect3(() => {
1694
+ }, [client, ready, refresh]);
1695
+ useEffect4(() => {
1515
1696
  if (!("onNotice" in client)) return;
1516
1697
  return client.onNotice(
1517
1698
  (n) => setConfig((c) => c ? { ...c, unseen: [...c.unseen, n] } : c)
1518
1699
  );
1519
1700
  }, [client]);
1520
- const dismissWhatsNew = useCallback2(() => {
1701
+ const dismissWhatsNew = useCallback3(() => {
1521
1702
  setWhatsNew(null);
1522
1703
  setWhatsNewToast(false);
1523
1704
  try {
@@ -1527,7 +1708,7 @@ function YouAsked({
1527
1708
  client.whatsNewSeen().catch(() => {
1528
1709
  });
1529
1710
  }, [client, whatsNewKey]);
1530
- const pointAtIt = useCallback2(
1711
+ const pointAtIt = useCallback3(
1531
1712
  () => new Promise((resolve, reject) => {
1532
1713
  import("./capture-FYZGL54F.js").then(
1533
1714
  (m) => setCapture({ Layer: m.default, resolve }),
@@ -1536,20 +1717,20 @@ function YouAsked({
1536
1717
  }),
1537
1718
  []
1538
1719
  );
1539
- const open = useCallback2((options) => {
1720
+ const open = useCallback3((options) => {
1540
1721
  openerRef.current = document.activeElement;
1541
1722
  setRoute(toRoute(options));
1542
1723
  }, []);
1543
- const close = useCallback2(() => {
1724
+ const close = useCallback3(() => {
1544
1725
  setRoute(null);
1545
1726
  openerRef.current?.focus?.({ preventScroll: true });
1546
1727
  }, []);
1547
- useEffect3(() => {
1728
+ useEffect4(() => {
1548
1729
  const onOpen = (e) => open(e.detail);
1549
1730
  window.addEventListener("youasked:open", onOpen);
1550
1731
  return () => window.removeEventListener("youasked:open", onOpen);
1551
1732
  }, [open]);
1552
- const markSeen = useCallback2(
1733
+ const markSeen = useCallback3(
1553
1734
  (ids) => {
1554
1735
  if (!ids.length) return;
1555
1736
  setConfig((c) => c ? { ...c, unseen: c.unseen.filter((n) => !ids.includes(n.id)) } : c);
@@ -1588,11 +1769,22 @@ function YouAsked({
1588
1769
  );
1589
1770
  const standing = unseen.filter((n) => !coveredBy(whatsNew, n));
1590
1771
  const notice = standing.find((n) => n.kind === "shipped") ?? standing.find((n) => n.kind === "reply");
1591
- return /* @__PURE__ */ jsxs4(YouAskedContext.Provider, { value, children: [
1772
+ const nudgeSettings = useMemo(
1773
+ () => config && cards && !testMode ? resolveNudge(config.nudge, nudgeProp) : null,
1774
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1775
+ [Boolean(config), JSON.stringify(config?.nudge ?? null), cards, testMode, typeof nudgeProp === "object" ? JSON.stringify(nudgeProp) : nudgeProp]
1776
+ );
1777
+ const busy = route !== null || Boolean(notice) || whatsNewToast && Boolean(whatsNew);
1778
+ const nudge = useNudge(nudgeSettings, demo ? null : `youasked:nudge:${appKey}:${userId ?? "anon"}`, busy);
1779
+ useEffect4(() => {
1780
+ if (route !== null) nudge.engaged();
1781
+ }, [route, nudge.engaged]);
1782
+ if (testMode) return /* @__PURE__ */ jsx5(YouAskedContext.Provider, { value, children });
1783
+ return /* @__PURE__ */ jsxs5(YouAskedContext.Provider, { value, children: [
1592
1784
  children,
1593
1785
  mounted && createPortal(
1594
- /* @__PURE__ */ jsxs4("div", { className: "ya-scope", "data-ya-mode": tokens?.mode, "data-ya-skip": "", style: scopeStyle, children: [
1595
- trigger === "floating" && !capture && /* @__PURE__ */ jsx4(
1786
+ /* @__PURE__ */ jsxs5("div", { className: "ya-scope", "data-ya-mode": tokens?.mode, "data-ya-skip": "", style: scopeStyle, children: [
1787
+ trigger === "floating" && !capture && /* @__PURE__ */ jsx5(
1596
1788
  Launcher,
1597
1789
  {
1598
1790
  position,
@@ -1602,7 +1794,7 @@ function YouAsked({
1602
1794
  onClick: () => route ? close() : open()
1603
1795
  }
1604
1796
  ),
1605
- cards && !notice && whatsNewToast && whatsNew && route === null && /* @__PURE__ */ jsx4(
1797
+ cards && !notice && whatsNewToast && whatsNew && route === null && /* @__PURE__ */ jsx5(
1606
1798
  WhatsNewToast,
1607
1799
  {
1608
1800
  news: whatsNew,
@@ -1612,7 +1804,18 @@ function YouAsked({
1612
1804
  onDismiss: dismissWhatsNew
1613
1805
  }
1614
1806
  ),
1615
- cards && notice && route === null && /* @__PURE__ */ jsx4(
1807
+ nudgeSettings && nudge.show && !busy && /* @__PURE__ */ jsx5(
1808
+ NudgeToast,
1809
+ {
1810
+ settings: nudgeSettings,
1811
+ appName: appName ?? config?.app.name ?? "us",
1812
+ position,
1813
+ trigger,
1814
+ onOpen: () => open({ view: "new" }),
1815
+ onDismiss: nudge.dismiss
1816
+ }
1817
+ ),
1818
+ cards && notice && route === null && /* @__PURE__ */ jsx5(
1616
1819
  ShippedToast,
1617
1820
  {
1618
1821
  notice,
@@ -1625,7 +1828,7 @@ function YouAsked({
1625
1828
  onDismiss: () => markSeen([notice.id])
1626
1829
  }
1627
1830
  ),
1628
- route && /* @__PURE__ */ jsx4(
1831
+ route && /* @__PURE__ */ jsx5(
1629
1832
  Panel,
1630
1833
  {
1631
1834
  route,
@@ -1636,7 +1839,7 @@ function YouAsked({
1636
1839
  hidden: capture !== null
1637
1840
  }
1638
1841
  ),
1639
- capture && /* @__PURE__ */ jsx4(
1842
+ capture && /* @__PURE__ */ jsx5(
1640
1843
  capture.Layer,
1641
1844
  {
1642
1845
  onDone: (image) => {
@@ -1663,8 +1866,8 @@ function findPageIcon() {
1663
1866
  }
1664
1867
  function useTokens(appearance, mounted) {
1665
1868
  const key = JSON.stringify(appearance ?? {});
1666
- const [tokens, setTokens] = useState3(null);
1667
- useEffect3(() => {
1869
+ const [tokens, setTokens] = useState4(null);
1870
+ useEffect4(() => {
1668
1871
  if (!mounted) return;
1669
1872
  const parsed = JSON.parse(key);
1670
1873
  const update = () => setTokens(resolveTokens(parsed));
@@ -1681,15 +1884,15 @@ function useTokens(appearance, mounted) {
1681
1884
  return tokens;
1682
1885
  }
1683
1886
  function useMedia(query) {
1684
- const [matches, setMatches] = useState3(() => typeof window !== "undefined" && window.matchMedia(query).matches);
1685
- useEffect3(() => {
1686
- const m = window.matchMedia(query);
1687
- const on = () => setMatches(m.matches);
1688
- on();
1689
- m.addEventListener("change", on);
1690
- return () => m.removeEventListener("change", on);
1691
- }, [query]);
1692
- return matches;
1887
+ return useSyncExternalStore(
1888
+ (onChange) => {
1889
+ const m = window.matchMedia(query);
1890
+ m.addEventListener("change", onChange);
1891
+ return () => m.removeEventListener("change", onChange);
1892
+ },
1893
+ () => window.matchMedia(query).matches,
1894
+ () => false
1895
+ );
1693
1896
  }
1694
1897
  function Panel({
1695
1898
  route,
@@ -1699,20 +1902,20 @@ function Panel({
1699
1902
  hasLauncher,
1700
1903
  hidden
1701
1904
  }) {
1702
- const panelRef = useRef3(null);
1703
- const backdropRef = useRef3(null);
1704
- const [closing, setClosing] = useState3(false);
1905
+ const panelRef = useRef4(null);
1906
+ const backdropRef = useRef4(null);
1907
+ const [closing, setClosing] = useState4(false);
1705
1908
  const isSheet = useMedia("(max-width: 639px)");
1706
1909
  const reduceMotion = useMedia("(prefers-reduced-motion: reduce)");
1707
- const hiddenRef = useRef3(hidden);
1910
+ const hiddenRef = useRef4(hidden);
1708
1911
  hiddenRef.current = hidden;
1709
- const drag = useRef3(null);
1710
- const requestClose = useCallback2(() => {
1912
+ const drag = useRef4(null);
1913
+ const requestClose = useCallback3(() => {
1711
1914
  if (reduceMotion) return onClose();
1712
1915
  setClosing(true);
1713
1916
  setTimeout(onClose, 200);
1714
1917
  }, [onClose, reduceMotion]);
1715
- useEffect3(() => {
1918
+ useEffect4(() => {
1716
1919
  if (!isSheet) return;
1717
1920
  const html = document.documentElement;
1718
1921
  const previous = html.style.overflow;
@@ -1721,10 +1924,10 @@ function Panel({
1721
1924
  html.style.overflow = previous;
1722
1925
  };
1723
1926
  }, [isSheet]);
1724
- useEffect3(() => {
1927
+ useEffect4(() => {
1725
1928
  panelRef.current?.focus({ preventScroll: true });
1726
1929
  }, []);
1727
- useEffect3(() => {
1930
+ useEffect4(() => {
1728
1931
  const onKey = (e) => {
1729
1932
  if (hiddenRef.current) return;
1730
1933
  if (e.key === "Escape") {
@@ -1778,7 +1981,7 @@ function Panel({
1778
1981
  if (backdrop) backdrop.style.opacity = "";
1779
1982
  }
1780
1983
  };
1781
- return /* @__PURE__ */ jsxs4(
1984
+ return /* @__PURE__ */ jsxs5(
1782
1985
  "div",
1783
1986
  {
1784
1987
  className: "ya-overlay",
@@ -1787,8 +1990,8 @@ function Panel({
1787
1990
  "data-closing": closing || void 0,
1788
1991
  hidden,
1789
1992
  children: [
1790
- /* @__PURE__ */ jsx4("div", { className: "ya-backdrop", ref: backdropRef, onClick: requestClose, "aria-hidden": "true" }),
1791
- /* @__PURE__ */ jsxs4(
1993
+ /* @__PURE__ */ jsx5("div", { className: "ya-backdrop", ref: backdropRef, onClick: requestClose, "aria-hidden": "true" }),
1994
+ /* @__PURE__ */ jsxs5(
1792
1995
  "div",
1793
1996
  {
1794
1997
  className: "ya-panel",
@@ -1802,8 +2005,8 @@ function Panel({
1802
2005
  onPointerUp,
1803
2006
  onPointerCancel: onPointerUp,
1804
2007
  children: [
1805
- /* @__PURE__ */ jsx4("div", { className: "ya-grab-zone", "aria-hidden": "true", children: /* @__PURE__ */ jsx4("div", { className: "ya-grab" }) }),
1806
- /* @__PURE__ */ jsx4(Navigator, { route, setRoute, onClose: requestClose })
2008
+ /* @__PURE__ */ jsx5("div", { className: "ya-grab-zone", "aria-hidden": "true", children: /* @__PURE__ */ jsx5("div", { className: "ya-grab" }) }),
2009
+ /* @__PURE__ */ jsx5(Navigator, { route, setRoute, onClose: requestClose })
1807
2010
  ]
1808
2011
  }
1809
2012
  )
@@ -1833,7 +2036,7 @@ function Launcher({
1833
2036
  badge,
1834
2037
  onClick
1835
2038
  }) {
1836
- return /* @__PURE__ */ jsxs4(
2039
+ return /* @__PURE__ */ jsxs5(
1837
2040
  "button",
1838
2041
  {
1839
2042
  type: "button",
@@ -1844,9 +2047,9 @@ function Launcher({
1844
2047
  "aria-label": isOpen ? "Close feature requests" : label || "Feature requests",
1845
2048
  "aria-expanded": isOpen,
1846
2049
  children: [
1847
- /* @__PURE__ */ jsx4("span", { className: "ya-fab-icon", children: isOpen ? /* @__PURE__ */ jsx4(IconClose, {}) : /* @__PURE__ */ jsx4(IconIdea, {}) }),
1848
- label && !isOpen && /* @__PURE__ */ jsx4("span", { children: label }),
1849
- badge && !isOpen && /* @__PURE__ */ jsx4("span", { className: "ya-dot", "aria-hidden": "true" })
2050
+ /* @__PURE__ */ jsx5("span", { className: "ya-fab-icon", children: isOpen ? /* @__PURE__ */ jsx5(IconClose, {}) : /* @__PURE__ */ jsx5(IconIdea, { ink: "var(--_accent)" }) }),
2051
+ label && !isOpen && /* @__PURE__ */ jsx5("span", { children: label }),
2052
+ badge && !isOpen && /* @__PURE__ */ jsx5("span", { className: "ya-dot", "aria-hidden": "true" })
1850
2053
  ]
1851
2054
  }
1852
2055
  );
@@ -1858,18 +2061,18 @@ function ShippedToast({
1858
2061
  onOpen,
1859
2062
  onDismiss
1860
2063
  }) {
1861
- return /* @__PURE__ */ jsx4("div", { className: "ya-toast", role: "status", "data-pos": position, "data-trigger": trigger, children: notice.kind === "reply" ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1862
- /* @__PURE__ */ jsxs4("div", { className: "ya-toast-top", children: [
1863
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-kicker", children: "New reply about" }),
1864
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx4(IconClose, {}) })
2064
+ return /* @__PURE__ */ jsx5("div", { className: "ya-toast", role: "status", "data-pos": position, "data-trigger": trigger, children: notice.kind === "reply" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
2065
+ /* @__PURE__ */ jsxs5("div", { className: "ya-toast-top", children: [
2066
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-kicker", children: "New reply about" }),
2067
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx5(IconClose, {}) })
1865
2068
  ] }),
1866
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-title", children: notice.title }),
1867
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-note ya-toast-quote", children: notice.reply }),
1868
- /* @__PURE__ */ jsxs4("div", { className: "ya-toast-actions", children: [
1869
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "Reply" }),
1870
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Later" })
2069
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-title", children: notice.title }),
2070
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-note ya-toast-quote", children: notice.reply }),
2071
+ /* @__PURE__ */ jsxs5("div", { className: "ya-toast-actions", children: [
2072
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "Reply" }),
2073
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Later" })
1871
2074
  ] })
1872
- ] }) : /* @__PURE__ */ jsx4(ShippedBody, { notice, onOpen, onDismiss }) });
2075
+ ] }) : /* @__PURE__ */ jsx5(ShippedBody, { notice, onOpen, onDismiss }) });
1873
2076
  }
1874
2077
  function WhatsNewToast({
1875
2078
  news,
@@ -1879,44 +2082,44 @@ function WhatsNewToast({
1879
2082
  onDismiss
1880
2083
  }) {
1881
2084
  const mine = news.items.some((i) => i.mine);
1882
- return /* @__PURE__ */ jsxs4("div", { className: "ya-toast", role: "status", "data-pos": position, "data-trigger": trigger, children: [
1883
- /* @__PURE__ */ jsxs4("div", { className: "ya-toast-top", children: [
1884
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-kicker", children: "What's new" }),
1885
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx4(IconClose, {}) })
2085
+ return /* @__PURE__ */ jsxs5("div", { className: "ya-toast", role: "status", "data-pos": position, "data-trigger": trigger, children: [
2086
+ /* @__PURE__ */ jsxs5("div", { className: "ya-toast-top", children: [
2087
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-kicker", children: "What's new" }),
2088
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx5(IconClose, {}) })
1886
2089
  ] }),
1887
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-title", children: whatsNewHeading(news.total) }),
1888
- mine && /* @__PURE__ */ jsxs4("div", { className: "ya-live", children: [
1889
- /* @__PURE__ */ jsx4(IconCheck, {}),
2090
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-title", children: whatsNewHeading(news.total) }),
2091
+ mine && /* @__PURE__ */ jsxs5("div", { className: "ya-live", children: [
2092
+ /* @__PURE__ */ jsx5(IconCheck, {}),
1890
2093
  "Including something you asked for"
1891
2094
  ] }),
1892
- /* @__PURE__ */ jsxs4("div", { className: "ya-toast-actions", children: [
1893
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "See what's new" }),
1894
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Not now" })
2095
+ /* @__PURE__ */ jsxs5("div", { className: "ya-toast-actions", children: [
2096
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "See what's new" }),
2097
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Not now" })
1895
2098
  ] })
1896
2099
  ] });
1897
2100
  }
1898
2101
  function ShippedBody({ notice, onOpen, onDismiss }) {
1899
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1900
- /* @__PURE__ */ jsxs4("div", { className: "ya-toast-top", children: [
1901
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-kicker", children: "You asked for" }),
1902
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx4(IconClose, {}) })
2102
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2103
+ /* @__PURE__ */ jsxs5("div", { className: "ya-toast-top", children: [
2104
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-kicker", children: "You asked for" }),
2105
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-icon-btn", onClick: onDismiss, "aria-label": "Dismiss", children: /* @__PURE__ */ jsx5(IconClose, {}) })
1903
2106
  ] }),
1904
- /* @__PURE__ */ jsx4("p", { className: "ya-toast-title", children: notice.title }),
1905
- /* @__PURE__ */ jsxs4("div", { className: "ya-live", children: [
1906
- /* @__PURE__ */ jsx4(IconCheck, {}),
2107
+ /* @__PURE__ */ jsx5("p", { className: "ya-toast-title", children: notice.title }),
2108
+ /* @__PURE__ */ jsxs5("div", { className: "ya-live", children: [
2109
+ /* @__PURE__ */ jsx5(IconCheck, {}),
1907
2110
  "It's live"
1908
2111
  ] }),
1909
- notice.shipNote && /* @__PURE__ */ jsx4("p", { className: "ya-toast-note", children: notice.shipNote }),
1910
- /* @__PURE__ */ jsxs4("div", { className: "ya-toast-actions", children: [
1911
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "See it" }),
1912
- /* @__PURE__ */ jsx4("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Nice" })
2112
+ notice.shipNote && /* @__PURE__ */ jsx5("p", { className: "ya-toast-note", children: notice.shipNote }),
2113
+ /* @__PURE__ */ jsxs5("div", { className: "ya-toast-actions", children: [
2114
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: onOpen, children: "See it" }),
2115
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: onDismiss, children: "Nice" })
1913
2116
  ] })
1914
2117
  ] });
1915
2118
  }
1916
2119
 
1917
2120
  // src/entries.tsx
1918
- import { useState as useState4 } from "react";
1919
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2121
+ import { useState as useState5 } from "react";
2122
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1920
2123
  function useScope(className, style) {
1921
2124
  const { scope } = useInternal();
1922
2125
  return {
@@ -1927,11 +2130,11 @@ function useScope(className, style) {
1927
2130
  }
1928
2131
  function AskBar({ placeholder = "Ask for a feature\u2026", className, style }) {
1929
2132
  const { open, appIcon, unseenCount } = useInternal();
1930
- return /* @__PURE__ */ jsx5("div", { ...useScope(className, style), children: /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-askbar", onClick: () => open({ view: "new" }), children: [
1931
- appIcon ? /* @__PURE__ */ jsx5(AppAvatar, { icon: appIcon, size: 30 }) : /* @__PURE__ */ jsx5("span", { className: "ya-askbar-glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(IconIdea, {}) }),
1932
- /* @__PURE__ */ jsx5("span", { className: "ya-askbar-text", children: placeholder }),
1933
- unseenCount > 0 && /* @__PURE__ */ jsx5("span", { className: "ya-badge", children: unseenCount }),
1934
- /* @__PURE__ */ jsx5("span", { className: "ya-askbar-send", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(IconSend, {}) })
2133
+ return /* @__PURE__ */ jsx6("div", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-askbar", onClick: () => open({ view: "new" }), children: [
2134
+ appIcon ? /* @__PURE__ */ jsx6(AppAvatar, { icon: appIcon, size: 30 }) : /* @__PURE__ */ jsx6("span", { className: "ya-askbar-glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconIdea, {}) }),
2135
+ /* @__PURE__ */ jsx6("span", { className: "ya-askbar-text", children: placeholder }),
2136
+ unseenCount > 0 && /* @__PURE__ */ jsx6("span", { className: "ya-badge", children: unseenCount }),
2137
+ /* @__PURE__ */ jsx6("span", { className: "ya-askbar-send", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconSend, {}) })
1935
2138
  ] }) });
1936
2139
  }
1937
2140
  function FeatureCard({
@@ -1944,15 +2147,15 @@ function FeatureCard({
1944
2147
  const { client, open, config, markSeen } = useInternal();
1945
2148
  const { data } = useLoad(() => client.list("top", "active"), [client]);
1946
2149
  const notice = config?.unseen.find((n) => n.kind === "shipped");
1947
- return /* @__PURE__ */ jsx5("div", { ...useScope(className, style), children: /* @__PURE__ */ jsxs5("section", { className: "ya-card", children: [
1948
- /* @__PURE__ */ jsxs5("div", { className: "ya-card-head", children: [
1949
- /* @__PURE__ */ jsx5("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(IconIdea, {}) }),
1950
- /* @__PURE__ */ jsxs5("div", { children: [
1951
- /* @__PURE__ */ jsx5("h3", { className: "ya-card-title", children: heading }),
1952
- /* @__PURE__ */ jsx5("p", { className: "ya-card-sub", children: description })
2150
+ return /* @__PURE__ */ jsx6("div", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("section", { className: "ya-card", children: [
2151
+ /* @__PURE__ */ jsxs6("div", { className: "ya-card-head", children: [
2152
+ /* @__PURE__ */ jsx6("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconIdea, {}) }),
2153
+ /* @__PURE__ */ jsxs6("div", { children: [
2154
+ /* @__PURE__ */ jsx6("h3", { className: "ya-card-title", children: heading }),
2155
+ /* @__PURE__ */ jsx6("p", { className: "ya-card-sub", children: description })
1953
2156
  ] })
1954
2157
  ] }),
1955
- notice && /* @__PURE__ */ jsxs5(
2158
+ notice && /* @__PURE__ */ jsxs6(
1956
2159
  "button",
1957
2160
  {
1958
2161
  type: "button",
@@ -1962,20 +2165,20 @@ function FeatureCard({
1962
2165
  open({ requestId: notice.requestId });
1963
2166
  },
1964
2167
  children: [
1965
- /* @__PURE__ */ jsx5("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx5(IconCheck, {}) }),
1966
- /* @__PURE__ */ jsxs5("span", { children: [
2168
+ /* @__PURE__ */ jsx6("span", { className: "ya-shipped-dot", children: /* @__PURE__ */ jsx6(IconCheck, {}) }),
2169
+ /* @__PURE__ */ jsxs6("span", { children: [
1967
2170
  "You asked for ",
1968
- /* @__PURE__ */ jsx5("b", { children: notice.title }),
2171
+ /* @__PURE__ */ jsx6("b", { children: notice.title }),
1969
2172
  ". It's live."
1970
2173
  ] }),
1971
- /* @__PURE__ */ jsx5(IconChevron, {})
2174
+ /* @__PURE__ */ jsx6(IconChevron, {})
1972
2175
  ]
1973
2176
  }
1974
2177
  ),
1975
- !data ? /* @__PURE__ */ jsx5(Skeleton, { rows: 2 }) : data.length > 0 ? /* @__PURE__ */ jsx5("ul", { className: "ya-list", children: data.slice(0, limit).map((r) => /* @__PURE__ */ jsx5(RequestRow, { client, request: r, compact: true, onOpen: () => open({ requestId: r.id }) }, r.id)) }) : null,
1976
- /* @__PURE__ */ jsxs5("div", { className: "ya-card-actions", children: [
1977
- /* @__PURE__ */ jsx5("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: () => open({ view: "new" }), children: "Suggest an idea" }),
1978
- data && data.length > limit && /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: () => open({ view: "board" }), children: [
2178
+ !data ? /* @__PURE__ */ jsx6(Skeleton, { rows: 2 }) : data.length > 0 ? /* @__PURE__ */ jsx6("ul", { className: "ya-list", children: data.slice(0, limit).map((r) => /* @__PURE__ */ jsx6(RequestRow, { client, request: r, compact: true, onOpen: () => open({ requestId: r.id }) }, r.id)) }) : null,
2179
+ /* @__PURE__ */ jsxs6("div", { className: "ya-card-actions", children: [
2180
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "ya-btn ya-btn-sm", onClick: () => open({ view: "new" }), children: "Suggest an idea" }),
2181
+ data && data.length > limit && /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-btn ya-btn-sm ya-btn-ghost", onClick: () => open({ view: "board" }), children: [
1979
2182
  "See all ",
1980
2183
  data.length
1981
2184
  ] })
@@ -1991,26 +2194,26 @@ function RequestButton({
1991
2194
  }) {
1992
2195
  const { open, unseenCount } = useInternal();
1993
2196
  const scope = useScope(className, style);
1994
- const badge = unseenCount > 0 && (variant === "icon" ? /* @__PURE__ */ jsx5("span", { className: "ya-dot", "aria-hidden": "true" }) : /* @__PURE__ */ jsx5("span", { className: "ya-badge", children: unseenCount }));
2197
+ const badge = unseenCount > 0 && (variant === "icon" ? /* @__PURE__ */ jsx6("span", { className: "ya-dot", "aria-hidden": "true" }) : /* @__PURE__ */ jsx6("span", { className: "ya-badge", children: unseenCount }));
1995
2198
  if (variant === "icon") {
1996
- return /* @__PURE__ */ jsx5("span", { ...scope, children: /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-iconbtn", onClick: () => open(), "aria-label": label, children: [
1997
- /* @__PURE__ */ jsx5(IconIdea, {}),
2199
+ return /* @__PURE__ */ jsx6("span", { ...scope, children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-iconbtn", onClick: () => open(), "aria-label": label, children: [
2200
+ /* @__PURE__ */ jsx6(IconIdea, {}),
1998
2201
  badge
1999
2202
  ] }) });
2000
2203
  }
2001
2204
  if (variant === "row") {
2002
- return /* @__PURE__ */ jsx5("div", { ...scope, children: /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-menurow", onClick: () => open(), children: [
2003
- /* @__PURE__ */ jsx5("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(IconIdea, {}) }),
2004
- /* @__PURE__ */ jsxs5("span", { className: "ya-menurow-text", children: [
2005
- /* @__PURE__ */ jsx5("span", { className: "ya-menurow-label", children: label }),
2006
- sublabel && /* @__PURE__ */ jsx5("span", { className: "ya-menurow-sub", children: sublabel })
2205
+ return /* @__PURE__ */ jsx6("div", { ...scope, children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-menurow", onClick: () => open(), children: [
2206
+ /* @__PURE__ */ jsx6("span", { className: "ya-card-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(IconIdea, {}) }),
2207
+ /* @__PURE__ */ jsxs6("span", { className: "ya-menurow-text", children: [
2208
+ /* @__PURE__ */ jsx6("span", { className: "ya-menurow-label", children: label }),
2209
+ sublabel && /* @__PURE__ */ jsx6("span", { className: "ya-menurow-sub", children: sublabel })
2007
2210
  ] }),
2008
2211
  badge,
2009
- /* @__PURE__ */ jsx5(IconChevron, {})
2212
+ /* @__PURE__ */ jsx6(IconChevron, {})
2010
2213
  ] }) });
2011
2214
  }
2012
- return /* @__PURE__ */ jsx5("span", { ...scope, children: /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-pillbtn", onClick: () => open(), children: [
2013
- /* @__PURE__ */ jsx5(IconIdea, {}),
2215
+ return /* @__PURE__ */ jsx6("span", { ...scope, children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-pillbtn", onClick: () => open(), children: [
2216
+ /* @__PURE__ */ jsx6(IconIdea, {}),
2014
2217
  label,
2015
2218
  badge
2016
2219
  ] }) });
@@ -2022,8 +2225,8 @@ function AskAbout({
2022
2225
  style
2023
2226
  }) {
2024
2227
  const { open } = useInternal();
2025
- return /* @__PURE__ */ jsx5("span", { ...useScope(className, style), children: /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-askabout", onClick: () => open({ view: "new", topic }), children: [
2026
- /* @__PURE__ */ jsx5(IconIdea, {}),
2228
+ return /* @__PURE__ */ jsx6("span", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-askabout", onClick: () => open({ view: "new", topic }), children: [
2229
+ /* @__PURE__ */ jsx6(IconIdea, {}),
2027
2230
  prompt ?? `Want more from ${topic}?`
2028
2231
  ] }) });
2029
2232
  }
@@ -2031,11 +2234,11 @@ function AskForIt({ query, className, style }) {
2031
2234
  const { open } = useInternal();
2032
2235
  const q = query.trim();
2033
2236
  const shown = q.length > 40 ? `${q.slice(0, 39).trimEnd()}\u2026` : q;
2034
- return /* @__PURE__ */ jsx5("span", { ...useScope(className, style), children: /* @__PURE__ */ jsxs5("button", { type: "button", className: "ya-askforit", onClick: () => open({ view: "new", draft: q, topic: "search" }), children: [
2035
- /* @__PURE__ */ jsx5("span", { children: shown ? `Can't find \u201C${shown}\u201D?` : "Can't find it?" }),
2036
- /* @__PURE__ */ jsxs5("b", { children: [
2237
+ return /* @__PURE__ */ jsx6("span", { ...useScope(className, style), children: /* @__PURE__ */ jsxs6("button", { type: "button", className: "ya-askforit", onClick: () => open({ view: "new", draft: q, topic: "search" }), children: [
2238
+ /* @__PURE__ */ jsx6("span", { children: shown ? `Can't find \u201C${shown}\u201D?` : "Can't find it?" }),
2239
+ /* @__PURE__ */ jsxs6("b", { children: [
2037
2240
  "Ask for it",
2038
- /* @__PURE__ */ jsx5(IconChevron, {})
2241
+ /* @__PURE__ */ jsx6(IconChevron, {})
2039
2242
  ] })
2040
2243
  ] }) });
2041
2244
  }
@@ -2045,8 +2248,8 @@ function FeatureBoard({
2045
2248
  className,
2046
2249
  style
2047
2250
  }) {
2048
- const [route, setRoute] = useState4(start === "board" ? { view: "board" } : { view: "home" });
2049
- return /* @__PURE__ */ jsx5("div", { ...useScope(className, style), children: /* @__PURE__ */ jsx5("div", { className: "ya-embed", style: { height }, children: /* @__PURE__ */ jsx5(Navigator, { route, setRoute }) }) });
2251
+ const [route, setRoute] = useState5(start === "board" ? { view: "board" } : { view: "home" });
2252
+ return /* @__PURE__ */ jsx6("div", { ...useScope(className, style), children: /* @__PURE__ */ jsx6("div", { className: "ya-embed", style: { height }, children: /* @__PURE__ */ jsx6(Navigator, { route, setRoute }) }) });
2050
2253
  }
2051
2254
  export {
2052
2255
  AskAbout,