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