@igstack/app-catalog-frontend-core 6.9.0 → 6.10.1

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.
Files changed (80) hide show
  1. package/dist/esm/__tests__/modules/appCatalog/ui/detail/CommunitySection.feed.test.d.ts +0 -0
  2. package/dist/esm/__tests__/modules/appCatalog/ui/detail/CommunitySection.help.test.d.ts +0 -0
  3. package/dist/esm/__tests__/modules/appCatalog/ui/detail/CommunitySection.input.test.d.ts +0 -0
  4. package/dist/esm/__tests__/modules/appCatalog/ui/detail/composeComments.test.d.ts +1 -0
  5. package/dist/esm/__tests__/ui/detail/CommunityInput.test.d.ts +1 -0
  6. package/dist/esm/modules/appCatalog/hooks/useUpdateApp.d.ts +8 -0
  7. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js +1 -0
  8. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js.map +1 -1
  9. package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js +237 -218
  10. package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js.map +1 -1
  11. package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.d.ts +17 -0
  12. package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.js +187 -0
  13. package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.js.map +1 -0
  14. package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.d.ts +9 -0
  15. package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.js +91 -0
  16. package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.js.map +1 -0
  17. package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.d.ts +17 -0
  18. package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.js +328 -0
  19. package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.js.map +1 -0
  20. package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.d.ts +15 -0
  21. package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.js +26 -0
  22. package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.js.map +1 -0
  23. package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.d.ts +8 -19
  24. package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js +49 -37
  25. package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js.map +1 -1
  26. package/dist/esm/modules/appCatalog/ui/detail/communityDraft.d.ts +30 -0
  27. package/dist/esm/modules/appCatalog/ui/detail/communityDraft.js +50 -0
  28. package/dist/esm/modules/appCatalog/ui/detail/communityDraft.js.map +1 -0
  29. package/dist/esm/modules/appCatalog/ui/detail/communityError.d.ts +6 -0
  30. package/dist/esm/modules/appCatalog/ui/detail/communityError.js +8 -0
  31. package/dist/esm/modules/appCatalog/ui/detail/communityError.js.map +1 -0
  32. package/dist/esm/modules/appCatalog/ui/detail/composeComments.d.ts +27 -0
  33. package/dist/esm/modules/appCatalog/ui/detail/composeComments.js +66 -0
  34. package/dist/esm/modules/appCatalog/ui/detail/composeComments.js.map +1 -0
  35. package/dist/esm/modules/appCatalog/ui/detail/usePendingCount.d.ts +11 -0
  36. package/dist/esm/modules/appCatalog/ui/detail/usePendingCount.js +15 -0
  37. package/dist/esm/modules/appCatalog/ui/detail/usePendingCount.js.map +1 -0
  38. package/dist/esm/modules/extensions/features/resourceDetail.d.ts +38 -0
  39. package/dist/esm/modules/extensions/features/resourceDetail.js +4 -0
  40. package/dist/esm/modules/extensions/features/resourceDetail.js.map +1 -1
  41. package/dist/esm/modules/extensions/index.d.ts +1 -1
  42. package/dist/esm/modules/extensions/index.js.map +1 -1
  43. package/dist/index.css +501 -49
  44. package/package.json +4 -4
  45. package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.appSwitch.test.tsx +5 -2
  46. package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.enter.test.tsx +5 -2
  47. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.background.test.tsx +9 -10
  48. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.behindThisCard.test.tsx +402 -0
  49. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.doorAndTabs.test.tsx +8 -4
  50. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.escape.test.tsx +5 -2
  51. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.knowMore.test.tsx +175 -0
  52. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.sectionOrder.test.tsx +5 -2
  53. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.subResourceTab.test.tsx +5 -2
  54. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.tags.test.tsx +5 -2
  55. package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.feed.test.tsx +281 -0
  56. package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.help.test.tsx +179 -0
  57. package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.input.test.tsx +423 -0
  58. package/src/__tests__/modules/appCatalog/ui/detail/composeComments.test.ts +170 -0
  59. package/src/__tests__/ui/detail/CommunityInput.test.tsx +39 -0
  60. package/src/index.css +501 -49
  61. package/src/modules/appCatalog/ui/detail/AppDetails.tsx +292 -289
  62. package/src/modules/appCatalog/ui/detail/CommentsFeed.tsx +254 -0
  63. package/src/modules/appCatalog/ui/detail/CommentsHelp.tsx +102 -0
  64. package/src/modules/appCatalog/ui/detail/CommunityInput.tsx +419 -0
  65. package/src/modules/appCatalog/ui/detail/CommunitySection.tsx +41 -0
  66. package/src/modules/appCatalog/ui/detail/DetailTabs.tsx +77 -71
  67. package/src/modules/appCatalog/ui/detail/communityDraft.ts +80 -0
  68. package/src/modules/appCatalog/ui/detail/communityError.ts +10 -0
  69. package/src/modules/appCatalog/ui/detail/composeComments.ts +103 -0
  70. package/src/modules/appCatalog/ui/detail/usePendingCount.ts +26 -0
  71. package/src/modules/extensions/features/resourceDetail.ts +28 -0
  72. package/src/modules/extensions/index.ts +2 -0
  73. package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.d.ts +0 -13
  74. package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.js +0 -433
  75. package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.js.map +0 -1
  76. package/src/__tests__/modules/appCatalog/ui/detail/FeedbackSection.emptyState.test.tsx +0 -181
  77. package/src/__tests__/modules/appCatalog/ui/detail/FeedbackSection.reviewStatus.test.tsx +0 -131
  78. package/src/modules/appCatalog/ui/detail/FeedbackSection.tsx +0 -605
  79. /package/dist/esm/__tests__/modules/appCatalog/ui/detail/{FeedbackSection.emptyState.test.d.ts → AppDetails.behindThisCard.test.d.ts} +0 -0
  80. /package/dist/esm/__tests__/modules/appCatalog/ui/detail/{FeedbackSection.reviewStatus.test.d.ts → AppDetails.knowMore.test.d.ts} +0 -0
@@ -1,11 +1,10 @@
1
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import { useSearch } from "@tanstack/react-router";
3
- import { ExternalLink, Trash2, Plus, CalendarPlus, Clock } from "lucide-react";
3
+ import { ExternalLink, Trash2, Plus } from "lucide-react";
4
4
  import React__default from "react";
5
5
  import { useHotkeys } from "react-hotkeys-hook";
6
6
  import { Badge } from "../../../../ui/badge.js";
7
7
  import { Button } from "../../../../ui/button.js";
8
- import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "../../../../ui/accordion.js";
9
8
  import { AccessRequestSection } from "../components/AccessRequestSection.js";
10
9
  import { AccessPrerequisiteChain } from "../components/AccessPrerequisiteChain.js";
11
10
  import { hasQuickJumps, QuickJumpBar } from "../components/QuickJumpBar.js";
@@ -13,6 +12,7 @@ import { useAppCatalogFilters } from "../context/AppCatalogFiltersContext.js";
13
12
  import { PersonBadge } from "../components/PersonBadge.js";
14
13
  import { useUser } from "../../../auth/AuthContext.js";
15
14
  import "@tanstack/react-query";
15
+ import "../../../auth/AuthModalContext.js";
16
16
  import { InlineEditableField } from "../components/InlineEditableField.js";
17
17
  import { MarkdownText } from "../components/MarkdownText.js";
18
18
  import { ScreenshotGallery } from "../components/ScreenshotGallery.js";
@@ -24,19 +24,29 @@ import { formatRelativeTime } from "../../utils/formatRelativeTime.js";
24
24
  import { TierVariantsSection } from "../components/TierVariantsSection.js";
25
25
  import { SubResourcesSection } from "../components/SubResourcesSection.js";
26
26
  import { getChildResources } from "../../utils/resolveHelpers.js";
27
- import { FeedbackSection } from "./FeedbackSection.js";
27
+ import { CommunitySection } from "./CommunitySection.js";
28
+ import { usePendingCount } from "./usePendingCount.js";
28
29
  import { displayUrl } from "../../utils/displayUrl.js";
29
30
  import { accessTabLabel } from "../../utils/accessTabLabel.js";
30
31
  import { AppDoor } from "./AppDoor.js";
31
32
  import { DetailTabs } from "./DetailTabs.js";
32
- import { ResourceDetailAccessActions } from "../../../extensions/features/resourceDetail.js";
33
+ import { ResourceDetailCardSource, ResourceDetailAccessActions } from "../../../extensions/features/resourceDetail.js";
33
34
  const TAB = {
34
35
  overview: "overview",
35
36
  access: "access",
36
37
  resources: "resources",
37
38
  documentation: "documentation",
38
- notes: "notes"
39
+ behind: "behind"
39
40
  };
41
+ function Group({
42
+ title,
43
+ children
44
+ }) {
45
+ return /* @__PURE__ */ jsxs("section", { className: "behind-group", children: [
46
+ /* @__PURE__ */ jsx("h2", { className: "behind-group__title", children: title }),
47
+ children
48
+ ] });
49
+ }
40
50
  function Section({
41
51
  title,
42
52
  hint,
@@ -79,10 +89,9 @@ function AppDetails({
79
89
  app,
80
90
  onAppClick
81
91
  }) {
82
- var _a, _b, _c;
92
+ var _a, _b, _c, _d, _e, _f, _g;
83
93
  const [isGalleryOpen, setIsGalleryOpen] = React__default.useState(false);
84
94
  const [galleryInitialIndex, setGalleryInitialIndex] = React__default.useState(0);
85
- const [composeRequests, setComposeRequests] = React__default.useState(0);
86
95
  const { approvalMethods, resources: allResources } = useAppCatalogContext();
87
96
  const { recordClick } = useAppClickHistory();
88
97
  const updateApp = useUpdateApp();
@@ -90,10 +99,33 @@ function AppDetails({
90
99
  const user = useUser();
91
100
  const isAdmin = (user == null ? void 0 : user.isAdmin) ?? false;
92
101
  const { state: filterState } = useAppCatalogFilters();
102
+ const behindCount = usePendingCount(app.slug, app.claims);
103
+ const lastChecked = (_a = app.freshness) == null ? void 0 : _a.lastCheckedAt;
104
+ const dateParts = [];
105
+ if (lastChecked) {
106
+ const suffix = ((_b = app.freshness) == null ? void 0 : _b.isStale) ? " (may be out of date)" : "";
107
+ dateParts.push(`Last checked ${formatRelativeTime(lastChecked)}${suffix}`);
108
+ }
109
+ if (app.createdAt) {
110
+ const addedLabel = dateParts.length > 0 ? "added" : "Added";
111
+ dateParts.push(`${addedLabel} ${formatRelativeTime(app.createdAt)}`);
112
+ }
113
+ const datesLine = dateParts.join(" · ");
114
+ const datesTitle = [
115
+ ((_c = app.freshness) == null ? void 0 : _c.lastContentChangeAt) && `Content last changed ${app.freshness.lastContentChangeAt}`,
116
+ lastChecked && `Last checked ${lastChecked}`,
117
+ app.createdAt && `Added ${app.createdAt}`
118
+ ].filter(Boolean).join(" · ");
119
+ const knowMoreRef = React__default.useRef(null);
120
+ const [shareAsk, setShareAsk] = React__default.useState(0);
121
+ React__default.useEffect(() => {
122
+ var _a2;
123
+ if (shareAsk) (_a2 = knowMoreRef.current) == null ? void 0 : _a2.focus();
124
+ }, [shareAsk]);
93
125
  const search = useSearch({ strict: false });
94
126
  const subSlug = search.sub;
95
127
  const displayTags = (app.tags ?? []).filter((tag) => !tag.includes(":"));
96
- const sourceUrls = ((_a = app.sources) == null ? void 0 : _a.map((s) => typeof s === "string" ? s : s.url)) ?? [];
128
+ const sourceUrls = ((_d = app.sources) == null ? void 0 : _d.map((s) => typeof s === "string" ? s : s.url)) ?? [];
97
129
  const displaySources = draftSource !== null ? [...sourceUrls, draftSource] : sourceUrls;
98
130
  const pulseByUrl = React__default.useMemo(() => {
99
131
  const byUrl = /* @__PURE__ */ new Map();
@@ -140,13 +172,14 @@ function AppDetails({
140
172
  setGalleryInitialIndex(index);
141
173
  setIsGalleryOpen(true);
142
174
  };
143
- const replacementApp = ((_b = app.deprecated) == null ? void 0 : _b.replacementSlug) ? allResources.find((a) => {
175
+ const replacementApp = ((_e = app.deprecated) == null ? void 0 : _e.replacementSlug) ? allResources.find((a) => {
144
176
  var _a2;
145
177
  return a.slug === ((_a2 = app.deprecated) == null ? void 0 : _a2.replacementSlug);
146
178
  }) : null;
147
179
  const access = accessTabLabel(app, approvalMethods);
148
- const hasResourcesTab = children.length > 0 || (((_c = app.tiers) == null ? void 0 : _c.length) ?? 0) > 0;
180
+ const hasResourcesTab = children.length > 0 || (((_f = app.tiers) == null ? void 0 : _f.length) ?? 0) > 0;
149
181
  const resourcesLabel = children.length ? app.childrenLabel ?? "Resources" : "Environments";
182
+ const hasDocumentationTab = (((_g = app.links) == null ? void 0 : _g.length) ?? 0) > 0 || !!app.background;
150
183
  const tabs = [
151
184
  {
152
185
  id: TAB.overview,
@@ -216,31 +249,154 @@ function AppDetails({
216
249
  }
217
250
  }
218
251
  ] : [],
252
+ ...hasDocumentationTab ? [
253
+ {
254
+ id: TAB.documentation,
255
+ label: "Documentation",
256
+ render: () => {
257
+ var _a2;
258
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
259
+ app.links && app.links.length > 0 && /* @__PURE__ */ jsx(Section, { title: "Documentation", first: true, children: /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: app.links.map((link) => /* @__PURE__ */ jsxs(
260
+ "a",
261
+ {
262
+ href: link.url,
263
+ target: "_blank",
264
+ rel: "noopener noreferrer",
265
+ className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-primary truncate",
266
+ children: [
267
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" }),
268
+ link.title || displayUrl(link.url)
269
+ ]
270
+ },
271
+ link.url
272
+ )) }) }),
273
+ app.background && /* @__PURE__ */ jsx(Section, { title: "Background", first: !((_a2 = app.links) == null ? void 0 : _a2.length), children: /* @__PURE__ */ jsx(MarkdownText, { className: "prose prose-sm text-muted-foreground max-w-none text-sm [&_p]:mt-0 [&_p]:mb-2 [&_p:last-child]:mb-0", children: app.background }) })
274
+ ] });
275
+ }
276
+ }
277
+ ] : [],
219
278
  {
220
- id: TAB.documentation,
221
- label: "Documentation",
279
+ id: TAB.behind,
280
+ label: "Behind this card",
281
+ chip: behindCount ? String(behindCount) : void 0,
282
+ ariaLabel: behindCount ? `Behind this card, ${behindCount} waiting for review` : void 0,
222
283
  render: () => {
223
- var _a2, _b2;
224
- return /* @__PURE__ */ jsxs(Fragment, { children: [
225
- app.links && app.links.length > 0 && /* @__PURE__ */ jsx(Section, { title: "Documentation", first: true, children: /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: app.links.map((link) => /* @__PURE__ */ jsxs(
226
- "a",
284
+ var _a2;
285
+ return /* @__PURE__ */ jsxs("div", { className: "behind-panel", children: [
286
+ /* @__PURE__ */ jsx(
287
+ CommunitySection,
227
288
  {
228
- href: link.url,
229
- target: "_blank",
230
- rel: "noopener noreferrer",
231
- className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-primary truncate",
232
- children: [
233
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" }),
234
- link.title || displayUrl(link.url)
235
- ]
236
- },
237
- link.url
238
- )) }) }),
239
- app.background && /* @__PURE__ */ jsx(Section, { title: "Background", children: /* @__PURE__ */ jsx(MarkdownText, { className: "prose prose-sm text-muted-foreground max-w-none text-sm [&_p]:mt-0 [&_p]:mb-2 [&_p:last-child]:mb-0", children: app.background }) }),
240
- /* @__PURE__ */ jsx(Section, { title: "Sources", first: !app.background, children: isAdmin ? /* @__PURE__ */ jsxs(Fragment, { children: [
241
- /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: displaySources.map((url, index) => {
242
- const isDraft = draftSource !== null && index === sourceUrls.length;
243
- return /* @__PURE__ */ jsxs(
289
+ ref: knowMoreRef,
290
+ appSlug: app.slug,
291
+ appName: app.displayName,
292
+ claims: app.claims
293
+ }
294
+ ),
295
+ (isAdmin || sourceUrls.length > 0 || app.createdAt || ((_a2 = app.freshness) == null ? void 0 : _a2.lastCheckedAt)) && /* @__PURE__ */ jsxs(Group, { title: "Kept up to date from", children: [
296
+ isAdmin ? /* @__PURE__ */ jsxs(Fragment, { children: [
297
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: displaySources.map((url, index) => {
298
+ const isDraft = draftSource !== null && index === sourceUrls.length;
299
+ return /* @__PURE__ */ jsxs(
300
+ "li",
301
+ {
302
+ className: "group flex items-center gap-2 text-xs",
303
+ children: [
304
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground shrink-0 tabular-nums", children: [
305
+ index + 1,
306
+ "."
307
+ ] }),
308
+ /* @__PURE__ */ jsx(
309
+ InlineEditableField,
310
+ {
311
+ value: url,
312
+ initialEditMode: isDraft,
313
+ onCancel: isDraft ? () => setDraftSource(null) : void 0,
314
+ onSave: (newUrl) => {
315
+ if (isDraft) {
316
+ setDraftSource(null);
317
+ if (newUrl) {
318
+ updateApp.mutate({
319
+ id: app.id,
320
+ data: { sources: [...sourceUrls, newUrl] }
321
+ });
322
+ }
323
+ } else {
324
+ const next = [...sourceUrls];
325
+ next[index] = newUrl;
326
+ updateApp.mutate({
327
+ id: app.id,
328
+ data: { sources: next.filter(Boolean) }
329
+ });
330
+ }
331
+ },
332
+ placeholder: "https://...",
333
+ viewClassName: "flex-1 min-w-0",
334
+ renderView: (val) => val ? /* @__PURE__ */ jsxs(
335
+ "a",
336
+ {
337
+ href: val,
338
+ target: "_blank",
339
+ rel: "noopener noreferrer",
340
+ className: "hover:text-primary inline-flex items-center gap-1 truncate",
341
+ children: [
342
+ displayUrl(val),
343
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
344
+ ]
345
+ }
346
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" })
347
+ }
348
+ ),
349
+ !isDraft && pulseByUrl.has(url) && /* @__PURE__ */ jsx(
350
+ SourcePulse,
351
+ {
352
+ pulse: pulseByUrl.get(url),
353
+ className: "ml-auto"
354
+ }
355
+ ),
356
+ !isDraft && /* @__PURE__ */ jsx(
357
+ Button,
358
+ {
359
+ type: "button",
360
+ variant: "ghost",
361
+ size: "icon-sm",
362
+ "aria-label": "Remove source",
363
+ className: "shrink-0 text-muted-foreground hover:text-destructive",
364
+ onClick: () => {
365
+ const next = sourceUrls.filter(
366
+ (_, i) => i !== index
367
+ );
368
+ updateApp.mutate({
369
+ id: app.id,
370
+ data: { sources: next }
371
+ });
372
+ },
373
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
374
+ }
375
+ )
376
+ ]
377
+ },
378
+ isDraft ? "draft" : `${index}-${url}`
379
+ );
380
+ }) }),
381
+ /* @__PURE__ */ jsxs(
382
+ Button,
383
+ {
384
+ type: "button",
385
+ variant: "ghost",
386
+ size: "sm",
387
+ className: "mt-2 gap-1 text-muted-foreground",
388
+ onClick: () => setDraftSource(""),
389
+ children: [
390
+ /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
391
+ "Add source"
392
+ ]
393
+ }
394
+ )
395
+ ] }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: sourceUrls.map((url, index) => (
396
+ // `group` so hovering anywhere on the row fades that source's
397
+ // track in — the mark sits in a right-hand column, and row
398
+ // hover is what ties the two ends of a wide row together.
399
+ /* @__PURE__ */ jsxs(
244
400
  "li",
245
401
  {
246
402
  className: "group flex items-center gap-2 text-xs",
@@ -249,191 +405,52 @@ function AppDetails({
249
405
  index + 1,
250
406
  "."
251
407
  ] }),
252
- /* @__PURE__ */ jsx(
253
- InlineEditableField,
408
+ url ? /* @__PURE__ */ jsxs(
409
+ "a",
254
410
  {
255
- value: url,
256
- initialEditMode: isDraft,
257
- onCancel: isDraft ? () => setDraftSource(null) : void 0,
258
- onSave: (newUrl) => {
259
- if (isDraft) {
260
- setDraftSource(null);
261
- if (newUrl) {
262
- updateApp.mutate({
263
- id: app.id,
264
- data: { sources: [...sourceUrls, newUrl] }
265
- });
266
- }
267
- } else {
268
- const next = [...sourceUrls];
269
- next[index] = newUrl;
270
- updateApp.mutate({
271
- id: app.id,
272
- data: { sources: next.filter(Boolean) }
273
- });
274
- }
275
- },
276
- placeholder: "https://...",
277
- viewClassName: "flex-1 min-w-0",
278
- renderView: (val) => val ? /* @__PURE__ */ jsxs(
279
- "a",
280
- {
281
- href: val,
282
- target: "_blank",
283
- rel: "noopener noreferrer",
284
- className: "hover:text-primary inline-flex items-center gap-1 truncate",
285
- children: [
286
- displayUrl(val),
287
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
288
- ]
289
- }
290
- ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" })
411
+ href: url,
412
+ target: "_blank",
413
+ rel: "noopener noreferrer",
414
+ className: "hover:text-primary inline-flex min-w-0 items-center gap-1 truncate",
415
+ children: [
416
+ displayUrl(url),
417
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
418
+ ]
291
419
  }
292
- ),
293
- !isDraft && pulseByUrl.has(url) && /* @__PURE__ */ jsx(
420
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" }),
421
+ pulseByUrl.has(url) && /* @__PURE__ */ jsx(
294
422
  SourcePulse,
295
423
  {
296
424
  pulse: pulseByUrl.get(url),
297
425
  className: "ml-auto"
298
426
  }
299
- ),
300
- !isDraft && /* @__PURE__ */ jsx(
301
- Button,
302
- {
303
- type: "button",
304
- variant: "ghost",
305
- size: "icon-sm",
306
- "aria-label": "Remove source",
307
- className: "shrink-0 text-muted-foreground hover:text-destructive",
308
- onClick: () => {
309
- const next = sourceUrls.filter(
310
- (_, i) => i !== index
311
- );
312
- updateApp.mutate({
313
- id: app.id,
314
- data: { sources: next }
315
- });
316
- },
317
- children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
318
- }
319
427
  )
320
428
  ]
321
429
  },
322
- isDraft ? "draft" : `${index}-${url}`
323
- );
324
- }) }),
325
- /* @__PURE__ */ jsxs(
326
- Button,
327
- {
328
- type: "button",
329
- variant: "ghost",
330
- size: "sm",
331
- className: "mt-2 gap-1 text-muted-foreground",
332
- onClick: () => setDraftSource(""),
333
- children: [
334
- /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
335
- "Add source"
336
- ]
337
- }
338
- )
339
- ] }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: sourceUrls.map((url, index) => (
340
- // `group` so hovering anywhere on the row fades that source's
341
- // track in — the mark sits in a right-hand column, and row
342
- // hover is what ties the two ends of a wide row together.
343
- /* @__PURE__ */ jsxs(
344
- "li",
345
- {
346
- className: "group flex items-center gap-2 text-xs",
347
- children: [
348
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground shrink-0 tabular-nums", children: [
349
- index + 1,
350
- "."
351
- ] }),
352
- url ? /* @__PURE__ */ jsxs(
353
- "a",
354
- {
355
- href: url,
356
- target: "_blank",
357
- rel: "noopener noreferrer",
358
- className: "hover:text-primary inline-flex min-w-0 items-center gap-1 truncate",
359
- children: [
360
- displayUrl(url),
361
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
362
- ]
363
- }
364
- ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" }),
365
- pulseByUrl.has(url) && /* @__PURE__ */ jsx(
366
- SourcePulse,
367
- {
368
- pulse: pulseByUrl.get(url),
369
- className: "ml-auto"
370
- }
371
- )
372
- ]
373
- },
374
- index
375
- )
376
- )) }) }),
377
- (app.createdAt || ((_a2 = app.freshness) == null ? void 0 : _a2.lastCheckedAt)) && /* @__PURE__ */ jsx(Section, { title: "Dates", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-5 gap-y-1", children: [
378
- app.createdAt && /* @__PURE__ */ jsxs(
379
- "span",
380
- {
381
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
382
- title: app.createdAt,
383
- children: [
384
- /* @__PURE__ */ jsx(CalendarPlus, { className: "size-3" }),
385
- "Added ",
386
- formatRelativeTime(app.createdAt)
387
- ]
388
- }
389
- ),
390
- ((_b2 = app.freshness) == null ? void 0 : _b2.lastCheckedAt) && /* @__PURE__ */ jsxs(
391
- "span",
392
- {
393
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
394
- title: app.freshness.lastContentChangeAt ? `Content last changed ${app.freshness.lastContentChangeAt} · last checked ${app.freshness.lastCheckedAt}` : `Last checked ${app.freshness.lastCheckedAt}`,
395
- children: [
396
- /* @__PURE__ */ jsx(Clock, { className: "size-3" }),
397
- "Updated",
398
- " ",
399
- formatRelativeTime(
400
- app.freshness.lastContentChangeAt ?? app.freshness.lastCheckedAt
401
- ),
402
- app.freshness.isStale && /* @__PURE__ */ jsxs("span", { className: "italic opacity-75", children: [
403
- " ",
404
- "· may be out of date"
405
- ] })
406
- ]
407
- }
408
- )
409
- ] }) }),
410
- (app.aiPrompt || app.aiMemory) && /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "mt-7", children: /* @__PURE__ */ jsxs(
411
- AccordionItem,
430
+ index
431
+ )
432
+ )) }),
433
+ datesLine && /* @__PURE__ */ jsx("p", { className: "behind-dates", title: datesTitle, children: datesLine })
434
+ ] }),
435
+ (app.aiPrompt || app.aiMemory) && /* @__PURE__ */ jsxs(Group, { title: "How the AI maintains this card", children: [
436
+ app.aiPrompt && /* @__PURE__ */ jsxs(Fragment, { children: [
437
+ /* @__PURE__ */ jsx("p", { className: "behind-ai__label", children: "Prompt" }),
438
+ /* @__PURE__ */ jsx("p", { className: "behind-ai", children: app.aiPrompt })
439
+ ] }),
440
+ app.aiMemory && /* @__PURE__ */ jsxs(Fragment, { children: [
441
+ /* @__PURE__ */ jsx("p", { className: "behind-ai__label", children: "Memory" }),
442
+ /* @__PURE__ */ jsx("p", { className: "behind-ai", children: app.aiMemory })
443
+ ] })
444
+ ] }),
445
+ /* @__PURE__ */ jsx("div", { className: "behind-card-source", children: /* @__PURE__ */ jsx(
446
+ ResourceDetailCardSource,
412
447
  {
413
- value: "technical-info",
414
- className: "border rounded-lg px-4",
415
- children: [
416
- /* @__PURE__ */ jsx(AccordionTrigger, { className: "text-sm hover:no-underline py-3", children: "Technical information" }),
417
- /* @__PURE__ */ jsxs(AccordionContent, { className: "pb-3 space-y-3", children: [
418
- app.aiPrompt && /* @__PURE__ */ jsxs("div", { children: [
419
- /* @__PURE__ */ jsx("h4", { className: "section-label mb-1", children: "AI Prompt" }),
420
- /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: app.aiPrompt })
421
- ] }),
422
- app.aiMemory && /* @__PURE__ */ jsxs("div", { children: [
423
- /* @__PURE__ */ jsx("h4", { className: "section-label mb-1", children: "AI Memory" }),
424
- /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: app.aiMemory })
425
- ] })
426
- ] })
427
- ]
448
+ appSlug: app.slug,
449
+ appName: app.displayName
428
450
  }
429
451
  ) })
430
452
  ] });
431
453
  }
432
- },
433
- {
434
- id: TAB.notes,
435
- label: "Notes & requests",
436
- render: () => /* @__PURE__ */ jsx(FeedbackSection, { appSlug: app.slug, openComposer: composeRequests })
437
454
  }
438
455
  ];
439
456
  const band = /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -446,19 +463,7 @@ function AppDetails({
446
463
  children: app.deprecated.type === "discouraged" ? "Discouraged" : "Deprecated"
447
464
  }
448
465
  ),
449
- /* @__PURE__ */ jsxs("div", { className: "ml-auto mr-10 flex items-center gap-2", children: [
450
- /* @__PURE__ */ jsx(ResourceDetailAccessActions, { ...accessActionsCtx }),
451
- /* @__PURE__ */ jsx(
452
- Button,
453
- {
454
- type: "button",
455
- size: "sm",
456
- variant: "outline",
457
- onClick: () => setComposeRequests((n) => n + 1),
458
- children: "✎ Suggest a change"
459
- }
460
- )
461
- ] })
466
+ /* @__PURE__ */ jsx("div", { className: "ml-auto mr-10 flex items-center gap-2", children: /* @__PURE__ */ jsx(ResourceDetailAccessActions, { ...accessActionsCtx }) })
462
467
  ] }),
463
468
  app.nicknames && app.nicknames.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-2.5 text-xs text-muted-foreground", children: [
464
469
  "Also known as ",
@@ -534,8 +539,22 @@ function AppDetails({
534
539
  band,
535
540
  bandClassName: "-mx-6 -mt-5 px-6 pt-5 sm:-mx-8 sm:-mt-7 sm:px-8 sm:pt-7",
536
541
  forcedTab: subSlug && hasResourcesTab ? TAB.resources : void 0,
537
- requestTab: composeRequests > 0 ? TAB.notes : void 0,
538
- requestNonce: composeRequests
542
+ trailing: (select) => /* @__PURE__ */ jsxs(
543
+ "button",
544
+ {
545
+ type: "button",
546
+ className: "detail-share",
547
+ "aria-label": "Share what you know",
548
+ onClick: () => {
549
+ select(TAB.behind);
550
+ setShareAsk((n) => n + 1);
551
+ },
552
+ children: [
553
+ /* @__PURE__ */ jsx("span", { className: "max-sm:sr-only", children: "Share what you know" }),
554
+ /* @__PURE__ */ jsx(Plus, { className: "size-4 sm:hidden", "aria-hidden": "true" })
555
+ ]
556
+ }
557
+ )
539
558
  }
540
559
  ) }),
541
560
  /* @__PURE__ */ jsx(