@igstack/app-catalog-frontend-core 6.3.1-alpha-20261003051755 → 6.5.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 (73) hide show
  1. package/dist/esm/__tests__/modules/appCatalog/ui/detail/AppDetails.subResourceTab.test.d.ts +0 -0
  2. package/dist/esm/__tests__/modules/appCatalog/ui/detail/FeedbackSection.emptyState.test.d.ts +0 -0
  3. package/dist/esm/__tests__/modules/appCatalog/ui/detail/FeedbackSection.reviewStatus.test.d.ts +0 -0
  4. package/dist/esm/__tests__/modules/appCatalog/utils/accessTabLabel.test.d.ts +1 -0
  5. package/dist/esm/api/infra/trpc.d.ts +42 -21
  6. package/dist/esm/modules/appCatalog/hooks/useUpdateApp.d.ts +1 -0
  7. package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.js +19 -0
  8. package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.js.map +1 -1
  9. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js +17 -2
  10. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js.map +1 -1
  11. package/dist/esm/modules/appCatalog/ui/catalog/RequestAppDialog.d.ts +14 -0
  12. package/dist/esm/modules/appCatalog/ui/catalog/RequestAppDialog.js +115 -0
  13. package/dist/esm/modules/appCatalog/ui/catalog/RequestAppDialog.js.map +1 -0
  14. package/dist/esm/modules/appCatalog/ui/components/AccessPrerequisiteChain.js +1 -0
  15. package/dist/esm/modules/appCatalog/ui/components/AccessPrerequisiteChain.js.map +1 -1
  16. package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js +11 -4
  17. package/dist/esm/modules/appCatalog/ui/components/AccessRequestSection.js.map +1 -1
  18. package/dist/esm/modules/appCatalog/ui/components/QuickJumpBar.js +0 -4
  19. package/dist/esm/modules/appCatalog/ui/components/QuickJumpBar.js.map +1 -1
  20. package/dist/esm/modules/appCatalog/ui/components/SubResourceDetailPanel.js +4 -1
  21. package/dist/esm/modules/appCatalog/ui/components/SubResourceDetailPanel.js.map +1 -1
  22. package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js +377 -385
  23. package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js.map +1 -1
  24. package/dist/esm/modules/appCatalog/ui/detail/AppDoor.d.ts +20 -0
  25. package/dist/esm/modules/appCatalog/ui/detail/AppDoor.js +59 -0
  26. package/dist/esm/modules/appCatalog/ui/detail/AppDoor.js.map +1 -0
  27. package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.d.ts +64 -0
  28. package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js +90 -0
  29. package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js.map +1 -0
  30. package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.d.ts +13 -0
  31. package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.js +433 -0
  32. package/dist/esm/modules/appCatalog/ui/detail/FeedbackSection.js.map +1 -0
  33. package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js +18 -1
  34. package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js.map +1 -1
  35. package/dist/esm/modules/appCatalog/utils/accessTabLabel.d.ts +30 -0
  36. package/dist/esm/modules/appCatalog/utils/accessTabLabel.js +34 -0
  37. package/dist/esm/modules/appCatalog/utils/accessTabLabel.js.map +1 -0
  38. package/dist/index.css +269 -5
  39. package/package.json +4 -4
  40. package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.appSwitch.test.tsx +17 -3
  41. package/src/__tests__/modules/appCatalog/ui/catalog/RequestAppDialog.test.tsx +113 -0
  42. package/src/__tests__/modules/appCatalog/ui/components/AccessRequestSection.test.tsx +6 -4
  43. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.background.test.tsx +25 -10
  44. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.doorAndTabs.test.tsx +193 -0
  45. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.escape.test.tsx +2 -2
  46. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.sectionOrder.test.tsx +32 -9
  47. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.subResourceTab.test.tsx +125 -0
  48. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.tags.test.tsx +2 -2
  49. package/src/__tests__/modules/appCatalog/ui/detail/FeedbackSection.emptyState.test.tsx +181 -0
  50. package/src/__tests__/modules/appCatalog/ui/detail/FeedbackSection.reviewStatus.test.tsx +131 -0
  51. package/src/__tests__/modules/appCatalog/utils/accessTabLabel.test.ts +206 -0
  52. package/src/__tests__/ui/components/header/Header.loginHidden.test.tsx +1 -1
  53. package/src/index.css +269 -5
  54. package/src/modules/appCatalog/ui/catalog/AppCatalogGrid.tsx +24 -0
  55. package/src/modules/appCatalog/ui/catalog/AppDetailPanel.tsx +46 -2
  56. package/src/modules/appCatalog/ui/catalog/RequestAppDialog.tsx +169 -0
  57. package/src/modules/appCatalog/ui/components/AccessPrerequisiteChain.tsx +3 -0
  58. package/src/modules/appCatalog/ui/components/AccessRequestSection.tsx +9 -4
  59. package/src/modules/appCatalog/ui/components/SubResourceDetailPanel.tsx +4 -1
  60. package/src/modules/appCatalog/ui/detail/AppDetails.tsx +574 -554
  61. package/src/modules/appCatalog/ui/detail/AppDoor.tsx +97 -0
  62. package/src/modules/appCatalog/ui/detail/DetailTabs.tsx +195 -0
  63. package/src/modules/appCatalog/ui/detail/FeedbackSection.tsx +605 -0
  64. package/src/modules/appCatalog/ui/pages/AppCatalogPage.tsx +20 -1
  65. package/src/modules/appCatalog/utils/accessTabLabel.ts +95 -0
  66. package/dist/esm/modules/appCatalog/ui/detail/CommentsSection.d.ts +0 -3
  67. package/dist/esm/modules/appCatalog/ui/detail/CommentsSection.js +0 -228
  68. package/dist/esm/modules/appCatalog/ui/detail/CommentsSection.js.map +0 -1
  69. package/src/__tests__/modules/appCatalog/ui/detail/CommentsSection.emptyState.test.tsx +0 -64
  70. package/src/__tests__/modules/appCatalog/ui/detail/CommentsSection.reviewStatus.test.tsx +0 -93
  71. package/src/modules/appCatalog/ui/detail/CommentsSection.tsx +0 -296
  72. /package/dist/esm/__tests__/modules/appCatalog/ui/{detail/CommentsSection.emptyState.test.d.ts → catalog/RequestAppDialog.test.d.ts} +0 -0
  73. /package/dist/esm/__tests__/modules/appCatalog/ui/detail/{CommentsSection.reviewStatus.test.d.ts → AppDetails.doorAndTabs.test.d.ts} +0 -0
@@ -1,14 +1,14 @@
1
- import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
- import { ExternalLink, CalendarPlus, Clock, Trash2, Plus, AppWindow } from "lucide-react";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useSearch } from "@tanstack/react-router";
3
+ import { ExternalLink, Trash2, Plus, CalendarPlus, Clock } from "lucide-react";
3
4
  import React__default from "react";
4
5
  import { useHotkeys } from "react-hotkeys-hook";
5
- import { cn } from "../../../../lib/utils.js";
6
6
  import { Badge } from "../../../../ui/badge.js";
7
7
  import { Button } from "../../../../ui/button.js";
8
8
  import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "../../../../ui/accordion.js";
9
9
  import { AccessRequestSection } from "../components/AccessRequestSection.js";
10
10
  import { AccessPrerequisiteChain } from "../components/AccessPrerequisiteChain.js";
11
- import { LEAP, QUIET_FILL, LEAVE_FILL, hasQuickJumps, QuickJumpBar } from "../components/QuickJumpBar.js";
11
+ import { hasQuickJumps, QuickJumpBar } from "../components/QuickJumpBar.js";
12
12
  import { useAppCatalogFilters } from "../context/AppCatalogFiltersContext.js";
13
13
  import { PersonBadge } from "../components/PersonBadge.js";
14
14
  import { useUser } from "../../../auth/AuthContext.js";
@@ -25,34 +25,29 @@ import { formatRelativeTime } from "../../utils/formatRelativeTime.js";
25
25
  import { TierVariantsSection } from "../components/TierVariantsSection.js";
26
26
  import { SubResourcesSection } from "../components/SubResourcesSection.js";
27
27
  import { getChildResources } from "../../utils/resolveHelpers.js";
28
- import { CommentsSection } from "./CommentsSection.js";
28
+ import { FeedbackSection } from "./FeedbackSection.js";
29
29
  import { displayUrl } from "../../utils/displayUrl.js";
30
- function getIconUrl(iconName) {
31
- return `/api/icons/${iconName}`;
32
- }
33
- function AppIcon({ app, className }) {
34
- const [imageError, setImageError] = React__default.useState(false);
35
- if (app.iconName && !imageError) {
36
- return /* @__PURE__ */ jsx("div", { className: cn("size-12 shrink-0", className), children: /* @__PURE__ */ jsx(
37
- "img",
38
- {
39
- src: getIconUrl(app.iconName),
40
- alt: `${app.abbreviation || app.displayName} icon`,
41
- className: "size-12 rounded-lg object-contain",
42
- onError: () => setImageError(true)
43
- }
44
- ) });
45
- }
46
- return /* @__PURE__ */ jsx(
47
- "div",
48
- {
49
- className: cn(
50
- "flex items-center justify-center rounded-lg bg-primary/10 text-primary size-12 shrink-0",
51
- className
52
- ),
53
- children: /* @__PURE__ */ jsx(AppWindow, { className: "size-6" })
54
- }
55
- );
30
+ import { accessTabLabel } from "../../utils/accessTabLabel.js";
31
+ import { AppDoor } from "./AppDoor.js";
32
+ import { DetailTabs } from "./DetailTabs.js";
33
+ const TAB = {
34
+ overview: "overview",
35
+ access: "access",
36
+ resources: "resources",
37
+ documentation: "documentation",
38
+ notes: "notes"
39
+ };
40
+ function Section({
41
+ title,
42
+ hint,
43
+ first,
44
+ children
45
+ }) {
46
+ return /* @__PURE__ */ jsxs("div", { className: first ? "" : "mt-7", children: [
47
+ /* @__PURE__ */ jsx("h3", { className: "section-label mb-1.5", children: title }),
48
+ hint && /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: hint }),
49
+ children
50
+ ] });
56
51
  }
57
52
  function AppScreenshot({ app }) {
58
53
  var _a;
@@ -80,39 +75,24 @@ function AppScreenshot({ app }) {
80
75
  (imageError || isLoadingImage) && /* @__PURE__ */ jsx("div", { className: "w-full h-64 bg-muted/30 flex items-center justify-center text-muted-foreground text-sm", children: isLoadingImage ? "Loading screenshot..." : "No screenshot available" })
81
76
  ] }) });
82
77
  }
83
- function TiersAndSubResourcesPanel({ app }) {
84
- const { resources } = useAppCatalogContext();
85
- const { state: filterState } = useAppCatalogFilters();
86
- const appSubResources = React__default.useMemo(
87
- () => getChildResources(resources, app.slug),
88
- [resources, app.slug]
89
- );
90
- return /* @__PURE__ */ jsxs(Fragment, { children: [
91
- app.tiers && app.tiers.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(TierVariantsSection, { tiers: app.tiers }) }),
92
- appSubResources.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: /* @__PURE__ */ jsx(
93
- SubResourcesSection,
94
- {
95
- subResources: appSubResources,
96
- parentSlug: app.slug,
97
- initialSearch: filterState.searchValue
98
- }
99
- ) })
100
- ] });
101
- }
102
78
  function AppDetails({
103
79
  app,
104
80
  onAppClick,
105
81
  onClosePanel
106
82
  }) {
107
- var _a, _b, _c, _d;
83
+ var _a, _b, _c;
108
84
  const [isGalleryOpen, setIsGalleryOpen] = React__default.useState(false);
109
85
  const [galleryInitialIndex, setGalleryInitialIndex] = React__default.useState(0);
86
+ const [composeRequests, setComposeRequests] = React__default.useState(0);
110
87
  const { approvalMethods, resources: allResources } = useAppCatalogContext();
111
88
  const { recordClick } = useAppClickHistory();
112
89
  const updateApp = useUpdateApp();
113
90
  const [draftSource, setDraftSource] = React__default.useState(null);
114
91
  const user = useUser();
115
92
  const isAdmin = (user == null ? void 0 : user.isAdmin) ?? false;
93
+ const { state: filterState } = useAppCatalogFilters();
94
+ const search = useSearch({ strict: false });
95
+ const subSlug = search.sub;
116
96
  const displayTags = (app.tags ?? []).filter((tag) => !tag.includes(":"));
117
97
  const sourceUrls = ((_a = app.sources) == null ? void 0 : _a.map((s) => typeof s === "string" ? s : s.url)) ?? [];
118
98
  const displaySources = draftSource !== null ? [...sourceUrls, draftSource] : sourceUrls;
@@ -125,6 +105,10 @@ function AppDetails({
125
105
  }
126
106
  return byUrl;
127
107
  }, [app.sources]);
108
+ const children = React__default.useMemo(
109
+ () => getChildResources(allResources, app.slug),
110
+ [allResources, app.slug]
111
+ );
128
112
  useHotkeys(
129
113
  "enter",
130
114
  () => {
@@ -156,122 +140,15 @@ function AppDetails({
156
140
  var _a2;
157
141
  return a.slug === ((_a2 = app.deprecated) == null ? void 0 : _a2.replacementSlug);
158
142
  }) : null;
159
- return /* @__PURE__ */ jsxs(Fragment, { children: [
160
- /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col p-6", children: [
161
- /* @__PURE__ */ jsx("div", { className: "-order-2 border-b pb-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
162
- /* @__PURE__ */ jsx(AppIcon, { app, className: "size-16" }),
163
- /* @__PURE__ */ jsxs("div", { className: "-mx-3 flex-1 min-w-0", children: [
164
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3", children: [
165
- /* @__PURE__ */ jsx("div", { className: "font-serif text-2xl font-semibold min-w-0", children: app.abbreviation ? `${app.displayName} (${app.abbreviation})` : app.displayName }),
166
- app.deprecated && /* @__PURE__ */ jsx(
167
- Badge,
168
- {
169
- variant: app.deprecated.type === "discouraged" ? "secondary" : "destructive",
170
- children: app.deprecated.type === "discouraged" ? "Discouraged" : "Deprecated"
171
- }
172
- )
173
- ] }),
174
- isAdmin && /* @__PURE__ */ jsxs("div", { className: "mt-1 px-3", children: [
175
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mr-2", children: "Slug:" }),
176
- /* @__PURE__ */ jsx(
177
- InlineEditableField,
178
- {
179
- value: app.slug,
180
- onSave: (slug) => updateApp.mutate({ id: app.id, data: { slug } }),
181
- className: "text-sm"
182
- }
183
- )
184
- ] }),
185
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-stretch gap-3 px-3", children: [
186
- isAdmin ? /* @__PURE__ */ jsx(
187
- InlineEditableField,
188
- {
189
- value: app.appUrl ?? "",
190
- onSave: (appUrl) => updateApp.mutate({ id: app.id, data: { appUrl } }),
191
- placeholder: "App URL",
192
- renderView: (url) => url ? /* @__PURE__ */ jsxs(
193
- "a",
194
- {
195
- href: url,
196
- target: "_blank",
197
- rel: "noopener noreferrer",
198
- onClick: () => recordClick(app.slug),
199
- className: "inline-flex items-center gap-1 rounded-md py-1 text-sm text-blue-600 hover:bg-accent/30 hover:underline dark:text-blue-400 transition-all",
200
- children: [
201
- displayUrl(url),
202
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3.5 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity" })
203
- ]
204
- }
205
- ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" })
206
- }
207
- ) : app.appUrl ? /* @__PURE__ */ jsxs(
208
- "a",
209
- {
210
- href: app.appUrl,
211
- target: "_blank",
212
- rel: "noopener noreferrer",
213
- onClick: () => recordClick(app.slug),
214
- className: cn(
215
- // The row's one inset and icon gap, and its height stated
216
- // rather than left to the taller neighbour.
217
- "inline-flex min-h-[34px] items-center gap-1.5 rounded-md px-3 text-sm font-medium shadow-sm",
218
- // Only ever ONE solid brand fill in this row: it goes to
219
- // Quick Jump's submit when there is one, because that is
220
- // the control that acts on something the user typed.
221
- hasQuickJumps(app) ? QUIET_FILL : LEAVE_FILL,
222
- LEAP
223
- ),
224
- title: displayUrl(app.appUrl),
225
- "aria-label": `Open ${app.displayName} in a new tab (${displayUrl(app.appUrl)})`,
226
- children: [
227
- /* @__PURE__ */ jsxs("span", { className: "max-w-[280px] truncate whitespace-nowrap", children: [
228
- "Go to ",
229
- displayUrl(app.appUrl)
230
- ] }),
231
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3.5 shrink-0" })
232
- ]
233
- }
234
- ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" }),
235
- /* @__PURE__ */ jsx(QuickJumpBar, { app })
236
- ] })
237
- ] })
238
- ] }) }),
239
- app.deprecated && (() => {
240
- const deprecationType = app.deprecated.type || "deprecated";
241
- const isDiscouraged = deprecationType === "discouraged";
242
- return /* @__PURE__ */ jsxs(
243
- "div",
244
- {
245
- className: isDiscouraged ? "mt-6 p-4 border border-yellow-500/50 rounded-lg bg-yellow-50 dark:bg-yellow-950/20" : "mt-6 p-4 border border-destructive/50 rounded-lg bg-destructive/10",
246
- children: [
247
- /* @__PURE__ */ jsx(
248
- "h3",
249
- {
250
- className: isDiscouraged ? "text-sm font-semibold text-yellow-700 dark:text-yellow-500 mb-2" : "text-sm font-semibold text-destructive mb-2",
251
- children: isDiscouraged ? "Usage discouraged" : "This application is deprecated"
252
- }
253
- ),
254
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: app.deprecated.comment }),
255
- replacementApp && /* @__PURE__ */ jsxs(
256
- "button",
257
- {
258
- type: "button",
259
- onClick: () => onAppClick == null ? void 0 : onAppClick(replacementApp),
260
- className: "text-sm font-medium text-primary hover:underline inline-flex items-center gap-1",
261
- children: [
262
- "View replacement: ",
263
- replacementApp.displayName,
264
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3" })
265
- ]
266
- }
267
- )
268
- ]
269
- }
270
- );
271
- })(),
272
- /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
273
- /* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-medium", children: "Description" }),
274
- isAdmin ? /* @__PURE__ */ jsx(
143
+ const access = accessTabLabel(app, approvalMethods);
144
+ const hasResourcesTab = children.length > 0 || (((_c = app.tiers) == null ? void 0 : _c.length) ?? 0) > 0;
145
+ const resourcesLabel = children.length ? app.childrenLabel ?? "Resources" : "Environments";
146
+ const tabs = [
147
+ {
148
+ id: TAB.overview,
149
+ label: "Overview",
150
+ render: () => /* @__PURE__ */ jsxs(Fragment, { children: [
151
+ /* @__PURE__ */ jsx(Section, { title: "Description", first: true, children: isAdmin ? /* @__PURE__ */ jsx(
275
152
  InlineEditableField,
276
153
  {
277
154
  value: app.description ?? "",
@@ -280,17 +157,8 @@ function AppDetails({
280
157
  placeholder: "Description",
281
158
  className: "min-h-[4rem] resize-y text-sm text-muted-foreground"
282
159
  }
283
- ) : app.description ? /* @__PURE__ */ jsx(MarkdownText, { className: "prose prose-sm max-w-none text-sm text-muted-foreground [&_p]:m-0", children: app.description }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "—" })
284
- ] }),
285
- /* @__PURE__ */ jsx(AccessPrerequisiteChain, { resource: app, onOpenParent: onAppClick }),
286
- /* @__PURE__ */ jsx(AccessRequestSection, { app, approvalMethods }),
287
- app.screenshotIds && app.screenshotIds.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
288
- /* @__PURE__ */ jsxs("h3", { className: "mb-2 text-sm font-medium", children: [
289
- "Screenshots (",
290
- app.screenshotIds.length,
291
- ")"
292
- ] }),
293
- /* @__PURE__ */ jsxs(
160
+ ) : app.description ? /* @__PURE__ */ jsx(MarkdownText, { className: "prose prose-sm max-w-none text-sm text-muted-foreground [&_p]:m-0", children: app.description }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "—" }) }),
161
+ app.screenshotIds && app.screenshotIds.length > 0 && /* @__PURE__ */ jsx(Section, { title: `Screenshots (${app.screenshotIds.length})`, children: /* @__PURE__ */ jsxs(
294
162
  "button",
295
163
  {
296
164
  type: "button",
@@ -306,102 +174,167 @@ function AppDetails({
306
174
  ] })
307
175
  ]
308
176
  }
309
- )
310
- ] }),
311
- app.ownerPersonSlug && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
312
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-sm font-medium", children: "Owner" }),
313
- /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: "Who is responsible for this resource" }),
314
- /* @__PURE__ */ jsx(PersonBadge, { slug: app.ownerPersonSlug })
315
- ] }),
316
- /* @__PURE__ */ jsx(TiersAndSubResourcesPanel, { app }),
317
- app.links && app.links.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-4", children: [
318
- /* @__PURE__ */ jsx("h3", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "Links" }),
319
- /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: app.links.map((link) => /* @__PURE__ */ jsxs(
320
- "a",
321
- {
322
- href: link.url,
323
- target: "_blank",
324
- rel: "noopener noreferrer",
325
- className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-primary truncate",
326
- children: [
327
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" }),
328
- link.title || displayUrl(link.url)
329
- ]
330
- },
331
- link.url
332
- )) })
333
- ] }),
334
- displayTags.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
335
- /* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-medium", children: "Tags" }),
336
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: displayTags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: tag }, tag)) })
337
- ] }),
338
- app.teams && app.teams.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
339
- /* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-medium", children: "Teams" }),
340
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: app.teams.map((team) => /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: team }, team)) })
341
- ] }),
342
- app.background && /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
343
- /* @__PURE__ */ jsx("h3", { className: "text-muted-foreground mb-2 text-xs font-medium", children: "Background" }),
344
- /* @__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 })
345
- ] }),
346
- (app.aiPrompt || app.aiMemory) && /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "mt-6", children: /* @__PURE__ */ jsxs(
347
- AccordionItem,
348
- {
349
- value: "technical-info",
350
- className: "border rounded-lg px-4",
351
- children: [
352
- /* @__PURE__ */ jsx(AccordionTrigger, { className: "text-sm hover:no-underline py-3", children: "Technical information" }),
353
- /* @__PURE__ */ jsxs(AccordionContent, { className: "pb-3 space-y-3", children: [
354
- app.aiPrompt && /* @__PURE__ */ jsxs("div", { children: [
355
- /* @__PURE__ */ jsx("h4", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "AI Prompt" }),
356
- /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: app.aiPrompt })
357
- ] }),
358
- app.aiMemory && /* @__PURE__ */ jsxs("div", { children: [
359
- /* @__PURE__ */ jsx("h4", { className: "mb-1 text-xs font-medium text-muted-foreground", children: "AI Memory" }),
360
- /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: app.aiMemory })
361
- ] })
362
- ] })
363
- ]
177
+ ) }),
178
+ app.ownerPersonSlug && /* @__PURE__ */ jsx(Section, { title: "Owner", hint: "Who is responsible for this resource", children: /* @__PURE__ */ jsx(PersonBadge, { slug: app.ownerPersonSlug }) }),
179
+ displayTags.length > 0 && /* @__PURE__ */ jsx(Section, { title: "Tags", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: displayTags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: tag }, tag)) }) }),
180
+ app.teams && app.teams.length > 0 && /* @__PURE__ */ jsx(Section, { title: "Teams", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: app.teams.map((team) => /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-xs", children: team }, team)) }) })
181
+ ] })
182
+ },
183
+ {
184
+ id: TAB.access,
185
+ label: `Access — ${access.route}`,
186
+ chip: access.chip,
187
+ render: () => /* @__PURE__ */ jsxs(Fragment, { children: [
188
+ /* @__PURE__ */ jsx(AccessPrerequisiteChain, { resource: app, onOpenParent: onAppClick }),
189
+ /* @__PURE__ */ jsx(AccessRequestSection, { app, approvalMethods })
190
+ ] })
191
+ },
192
+ ...hasResourcesTab ? [
193
+ {
194
+ id: TAB.resources,
195
+ label: resourcesLabel,
196
+ chip: children.length ? String(children.length) : void 0,
197
+ render: () => {
198
+ var _a2;
199
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
200
+ app.tiers && app.tiers.length > 0 && /* @__PURE__ */ jsx(TierVariantsSection, { tiers: app.tiers }),
201
+ children.length > 0 && /* @__PURE__ */ jsx("div", { className: ((_a2 = app.tiers) == null ? void 0 : _a2.length) ? "mt-7" : "", children: /* @__PURE__ */ jsx(
202
+ SubResourcesSection,
203
+ {
204
+ subResources: children,
205
+ parentSlug: app.slug,
206
+ initialSearch: filterState.searchValue
207
+ }
208
+ ) })
209
+ ] });
364
210
  }
365
- ) }),
366
- (app.createdAt || ((_c = app.freshness) == null ? void 0 : _c.lastCheckedAt)) && /* @__PURE__ */ jsxs("div", { className: "mt-6 flex flex-wrap gap-x-5 gap-y-1", children: [
367
- app.createdAt && /* @__PURE__ */ jsxs(
368
- "span",
369
- {
370
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
371
- title: app.createdAt,
372
- children: [
373
- /* @__PURE__ */ jsx(CalendarPlus, { className: "size-3" }),
374
- "Added ",
375
- formatRelativeTime(app.createdAt)
376
- ]
377
- }
378
- ),
379
- ((_d = app.freshness) == null ? void 0 : _d.lastCheckedAt) && /* @__PURE__ */ jsxs(
380
- "span",
381
- {
382
- className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
383
- title: app.freshness.lastContentChangeAt ? `Content last changed ${app.freshness.lastContentChangeAt} · last checked ${app.freshness.lastCheckedAt}` : `Last checked ${app.freshness.lastCheckedAt}`,
384
- children: [
385
- /* @__PURE__ */ jsx(Clock, { className: "size-3" }),
386
- "Updated",
387
- " ",
388
- formatRelativeTime(
389
- app.freshness.lastContentChangeAt ?? app.freshness.lastCheckedAt
390
- ),
391
- app.freshness.isStale && /* @__PURE__ */ jsxs("span", { className: "italic opacity-75", children: [
392
- " ",
393
- "· may be out of date"
394
- ] })
395
- ]
396
- }
397
- )
398
- ] }),
399
- /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
400
- /* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-medium", children: "Sources" }),
401
- isAdmin ? /* @__PURE__ */ jsxs(Fragment, { children: [
402
- /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: displaySources.map((url, index) => {
403
- const isDraft = draftSource !== null && index === sourceUrls.length;
404
- return /* @__PURE__ */ jsxs(
211
+ }
212
+ ] : [],
213
+ {
214
+ id: TAB.documentation,
215
+ label: "Documentation",
216
+ render: () => {
217
+ var _a2, _b2;
218
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
219
+ 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(
220
+ "a",
221
+ {
222
+ href: link.url,
223
+ target: "_blank",
224
+ rel: "noopener noreferrer",
225
+ className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-primary truncate",
226
+ children: [
227
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" }),
228
+ link.title || displayUrl(link.url)
229
+ ]
230
+ },
231
+ link.url
232
+ )) }) }),
233
+ 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 }) }),
234
+ /* @__PURE__ */ jsx(Section, { title: "Sources", first: !app.background, children: isAdmin ? /* @__PURE__ */ jsxs(Fragment, { children: [
235
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: displaySources.map((url, index) => {
236
+ const isDraft = draftSource !== null && index === sourceUrls.length;
237
+ return /* @__PURE__ */ jsxs(
238
+ "li",
239
+ {
240
+ className: "group flex items-center gap-2 text-xs",
241
+ children: [
242
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground shrink-0 tabular-nums", children: [
243
+ index + 1,
244
+ "."
245
+ ] }),
246
+ /* @__PURE__ */ jsx(
247
+ InlineEditableField,
248
+ {
249
+ value: url,
250
+ initialEditMode: isDraft,
251
+ onCancel: isDraft ? () => setDraftSource(null) : void 0,
252
+ onSave: (newUrl) => {
253
+ if (isDraft) {
254
+ setDraftSource(null);
255
+ if (newUrl) {
256
+ updateApp.mutate({
257
+ id: app.id,
258
+ data: { sources: [...sourceUrls, newUrl] }
259
+ });
260
+ }
261
+ } else {
262
+ const next = [...sourceUrls];
263
+ next[index] = newUrl;
264
+ updateApp.mutate({
265
+ id: app.id,
266
+ data: { sources: next.filter(Boolean) }
267
+ });
268
+ }
269
+ },
270
+ placeholder: "https://...",
271
+ viewClassName: "flex-1 min-w-0",
272
+ renderView: (val) => val ? /* @__PURE__ */ jsxs(
273
+ "a",
274
+ {
275
+ href: val,
276
+ target: "_blank",
277
+ rel: "noopener noreferrer",
278
+ className: "hover:text-primary inline-flex items-center gap-1 truncate",
279
+ children: [
280
+ displayUrl(val),
281
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
282
+ ]
283
+ }
284
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" })
285
+ }
286
+ ),
287
+ !isDraft && pulseByUrl.has(url) && /* @__PURE__ */ jsx(
288
+ SourcePulse,
289
+ {
290
+ pulse: pulseByUrl.get(url),
291
+ className: "ml-auto"
292
+ }
293
+ ),
294
+ !isDraft && /* @__PURE__ */ jsx(
295
+ Button,
296
+ {
297
+ type: "button",
298
+ variant: "ghost",
299
+ size: "icon-sm",
300
+ "aria-label": "Remove source",
301
+ className: "shrink-0 text-muted-foreground hover:text-destructive",
302
+ onClick: () => {
303
+ const next = sourceUrls.filter(
304
+ (_, i) => i !== index
305
+ );
306
+ updateApp.mutate({
307
+ id: app.id,
308
+ data: { sources: next }
309
+ });
310
+ },
311
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
312
+ }
313
+ )
314
+ ]
315
+ },
316
+ isDraft ? "draft" : `${index}-${url}`
317
+ );
318
+ }) }),
319
+ /* @__PURE__ */ jsxs(
320
+ Button,
321
+ {
322
+ type: "button",
323
+ variant: "ghost",
324
+ size: "sm",
325
+ className: "mt-2 gap-1 text-muted-foreground",
326
+ onClick: () => setDraftSource(""),
327
+ children: [
328
+ /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
329
+ "Add source"
330
+ ]
331
+ }
332
+ )
333
+ ] }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: sourceUrls.map((url, index) => (
334
+ // `group` so hovering anywhere on the row fades that source's
335
+ // track in — the mark sits in a right-hand column, and row
336
+ // hover is what ties the two ends of a wide row together.
337
+ /* @__PURE__ */ jsxs(
405
338
  "li",
406
339
  {
407
340
  className: "group flex items-center gap-2 text-xs",
@@ -410,134 +343,193 @@ function AppDetails({
410
343
  index + 1,
411
344
  "."
412
345
  ] }),
413
- /* @__PURE__ */ jsx(
414
- InlineEditableField,
346
+ url ? /* @__PURE__ */ jsxs(
347
+ "a",
415
348
  {
416
- value: url,
417
- initialEditMode: isDraft,
418
- onCancel: isDraft ? () => setDraftSource(null) : void 0,
419
- onSave: (newUrl) => {
420
- if (isDraft) {
421
- setDraftSource(null);
422
- if (newUrl) {
423
- updateApp.mutate({
424
- id: app.id,
425
- data: { sources: [...sourceUrls, newUrl] }
426
- });
427
- }
428
- } else {
429
- const next = [...sourceUrls];
430
- next[index] = newUrl;
431
- updateApp.mutate({
432
- id: app.id,
433
- data: { sources: next.filter(Boolean) }
434
- });
435
- }
436
- },
437
- placeholder: "https://...",
438
- viewClassName: "flex-1 min-w-0",
439
- renderView: (val) => val ? /* @__PURE__ */ jsxs(
440
- "a",
441
- {
442
- href: val,
443
- target: "_blank",
444
- rel: "noopener noreferrer",
445
- className: "hover:text-primary inline-flex items-center gap-1 truncate",
446
- children: [
447
- displayUrl(val),
448
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
449
- ]
450
- }
451
- ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" })
349
+ href: url,
350
+ target: "_blank",
351
+ rel: "noopener noreferrer",
352
+ className: "hover:text-primary inline-flex min-w-0 items-center gap-1 truncate",
353
+ children: [
354
+ displayUrl(url),
355
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
356
+ ]
452
357
  }
453
- ),
454
- !isDraft && pulseByUrl.has(url) && /* @__PURE__ */ jsx(
358
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" }),
359
+ pulseByUrl.has(url) && /* @__PURE__ */ jsx(
455
360
  SourcePulse,
456
361
  {
457
362
  pulse: pulseByUrl.get(url),
458
363
  className: "ml-auto"
459
364
  }
460
- ),
461
- !isDraft && /* @__PURE__ */ jsx(
462
- Button,
463
- {
464
- type: "button",
465
- variant: "ghost",
466
- size: "icon-sm",
467
- "aria-label": "Remove source",
468
- className: "shrink-0 text-muted-foreground hover:text-destructive",
469
- onClick: () => {
470
- const next = sourceUrls.filter(
471
- (_, i) => i !== index
472
- );
473
- updateApp.mutate({
474
- id: app.id,
475
- data: { sources: next }
476
- });
477
- },
478
- children: /* @__PURE__ */ jsx(Trash2, { className: "size-3.5" })
479
- }
480
365
  )
481
366
  ]
482
367
  },
483
- isDraft ? "draft" : `${index}-${url}`
484
- );
485
- }) }),
486
- /* @__PURE__ */ jsxs(
487
- Button,
368
+ index
369
+ )
370
+ )) }) }),
371
+ (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: [
372
+ app.createdAt && /* @__PURE__ */ jsxs(
373
+ "span",
374
+ {
375
+ className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
376
+ title: app.createdAt,
377
+ children: [
378
+ /* @__PURE__ */ jsx(CalendarPlus, { className: "size-3" }),
379
+ "Added ",
380
+ formatRelativeTime(app.createdAt)
381
+ ]
382
+ }
383
+ ),
384
+ ((_b2 = app.freshness) == null ? void 0 : _b2.lastCheckedAt) && /* @__PURE__ */ jsxs(
385
+ "span",
386
+ {
387
+ className: "inline-flex items-center gap-1 text-xs text-muted-foreground",
388
+ title: app.freshness.lastContentChangeAt ? `Content last changed ${app.freshness.lastContentChangeAt} · last checked ${app.freshness.lastCheckedAt}` : `Last checked ${app.freshness.lastCheckedAt}`,
389
+ children: [
390
+ /* @__PURE__ */ jsx(Clock, { className: "size-3" }),
391
+ "Updated",
392
+ " ",
393
+ formatRelativeTime(
394
+ app.freshness.lastContentChangeAt ?? app.freshness.lastCheckedAt
395
+ ),
396
+ app.freshness.isStale && /* @__PURE__ */ jsxs("span", { className: "italic opacity-75", children: [
397
+ " ",
398
+ "· may be out of date"
399
+ ] })
400
+ ]
401
+ }
402
+ )
403
+ ] }) }),
404
+ (app.aiPrompt || app.aiMemory) && /* @__PURE__ */ jsx(Accordion, { type: "single", collapsible: true, className: "mt-7", children: /* @__PURE__ */ jsxs(
405
+ AccordionItem,
488
406
  {
489
- type: "button",
490
- variant: "ghost",
491
- size: "sm",
492
- className: "mt-2 gap-1 text-muted-foreground",
493
- onClick: () => setDraftSource(""),
407
+ value: "technical-info",
408
+ className: "border rounded-lg px-4",
494
409
  children: [
495
- /* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
496
- "Add source"
410
+ /* @__PURE__ */ jsx(AccordionTrigger, { className: "text-sm hover:no-underline py-3", children: "Technical information" }),
411
+ /* @__PURE__ */ jsxs(AccordionContent, { className: "pb-3 space-y-3", children: [
412
+ app.aiPrompt && /* @__PURE__ */ jsxs("div", { children: [
413
+ /* @__PURE__ */ jsx("h4", { className: "section-label mb-1", children: "AI Prompt" }),
414
+ /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: app.aiPrompt })
415
+ ] }),
416
+ app.aiMemory && /* @__PURE__ */ jsxs("div", { children: [
417
+ /* @__PURE__ */ jsx("h4", { className: "section-label mb-1", children: "AI Memory" }),
418
+ /* @__PURE__ */ jsx("p", { className: "text-xs whitespace-pre-wrap", children: app.aiMemory })
419
+ ] })
420
+ ] })
497
421
  ]
498
422
  }
499
- )
500
- ] }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: sourceUrls.map((url, index) => (
501
- // `group` so hovering anywhere on the row fades that source's
502
- // track in — the mark sits in a right-hand column, and row hover
503
- // is what ties the two ends of a wide row together.
504
- /* @__PURE__ */ jsxs(
505
- "li",
506
- {
507
- className: "group flex items-center gap-2 text-xs",
508
- children: [
509
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground shrink-0 tabular-nums", children: [
510
- index + 1,
511
- "."
512
- ] }),
513
- url ? /* @__PURE__ */ jsxs(
514
- "a",
515
- {
516
- href: url,
517
- target: "_blank",
518
- rel: "noopener noreferrer",
519
- className: "hover:text-primary inline-flex min-w-0 items-center gap-1 truncate",
520
- children: [
521
- displayUrl(url),
522
- /* @__PURE__ */ jsx(ExternalLink, { className: "size-3 shrink-0" })
523
- ]
524
- }
525
- ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "—" }),
526
- pulseByUrl.has(url) && /* @__PURE__ */ jsx(
527
- SourcePulse,
528
- {
529
- pulse: pulseByUrl.get(url),
530
- className: "ml-auto"
531
- }
532
- )
533
- ]
534
- },
535
- index
536
- )
537
- )) })
423
+ ) })
424
+ ] });
425
+ }
426
+ },
427
+ {
428
+ id: TAB.notes,
429
+ label: "Notes & requests",
430
+ render: () => /* @__PURE__ */ jsx(FeedbackSection, { appSlug: app.slug, openComposer: composeRequests })
431
+ }
432
+ ];
433
+ const band = /* @__PURE__ */ jsxs(Fragment, { children: [
434
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-7 gap-y-2", children: [
435
+ /* @__PURE__ */ jsx(AppDoor, { app, onOpen: () => recordClick(app.slug) }),
436
+ app.deprecated && /* @__PURE__ */ jsx(
437
+ Badge,
438
+ {
439
+ variant: app.deprecated.type === "discouraged" ? "secondary" : "destructive",
440
+ children: app.deprecated.type === "discouraged" ? "Discouraged" : "Deprecated"
441
+ }
442
+ ),
443
+ /* @__PURE__ */ jsx(
444
+ Button,
445
+ {
446
+ type: "button",
447
+ size: "sm",
448
+ variant: "outline",
449
+ className: "ml-auto mr-10",
450
+ onClick: () => setComposeRequests((n) => n + 1),
451
+ children: "✎ Suggest a change"
452
+ }
453
+ )
454
+ ] }),
455
+ app.nicknames && app.nicknames.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-2.5 text-xs text-muted-foreground", children: [
456
+ "Also known as ",
457
+ app.nicknames.join(", ")
458
+ ] }),
459
+ isAdmin && /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-x-6 gap-y-1", children: [
460
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
461
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Slug:" }),
462
+ /* @__PURE__ */ jsx(
463
+ InlineEditableField,
464
+ {
465
+ value: app.slug,
466
+ onSave: (slug) => updateApp.mutate({ id: app.id, data: { slug } }),
467
+ className: "text-sm"
468
+ }
469
+ )
538
470
  ] }),
539
- /* @__PURE__ */ jsx(CommentsSection, { appSlug: app.slug })
471
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
472
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "URL:" }),
473
+ /* @__PURE__ */ jsx(
474
+ InlineEditableField,
475
+ {
476
+ value: app.appUrl ?? "",
477
+ onSave: (appUrl) => updateApp.mutate({ id: app.id, data: { appUrl } }),
478
+ placeholder: "App URL",
479
+ className: "text-sm"
480
+ }
481
+ )
482
+ ] })
540
483
  ] }),
484
+ hasQuickJumps(app) && /* @__PURE__ */ jsxs("div", { className: "mt-4", children: [
485
+ /* @__PURE__ */ jsx("p", { className: "mb-1.5 text-xs text-muted-foreground", children: "Or go straight to a record:" }),
486
+ /* @__PURE__ */ jsx(QuickJumpBar, { app })
487
+ ] }),
488
+ app.deprecated && (() => {
489
+ const isDiscouraged = app.deprecated.type === "discouraged";
490
+ return /* @__PURE__ */ jsxs(
491
+ "div",
492
+ {
493
+ className: isDiscouraged ? "mt-4 rounded-lg border border-yellow-500/50 bg-yellow-50 p-4 dark:bg-yellow-950/20" : "mt-4 rounded-lg border border-destructive/50 bg-destructive/10 p-4",
494
+ children: [
495
+ /* @__PURE__ */ jsx(
496
+ "h3",
497
+ {
498
+ className: isDiscouraged ? "section-label mb-1.5 text-yellow-700 dark:text-yellow-500" : "section-label mb-1.5 text-destructive",
499
+ children: isDiscouraged ? "Usage discouraged" : "This application is deprecated"
500
+ }
501
+ ),
502
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: app.deprecated.comment }),
503
+ replacementApp && /* @__PURE__ */ jsxs(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ onClick: () => onAppClick == null ? void 0 : onAppClick(replacementApp),
508
+ className: "mt-3 inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline",
509
+ children: [
510
+ "View replacement: ",
511
+ replacementApp.displayName,
512
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-3" })
513
+ ]
514
+ }
515
+ )
516
+ ]
517
+ }
518
+ );
519
+ })()
520
+ ] });
521
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
522
+ /* @__PURE__ */ jsx("div", { className: "flex h-full flex-col", children: /* @__PURE__ */ jsx(
523
+ DetailTabs,
524
+ {
525
+ tabs,
526
+ band,
527
+ bandClassName: "-mx-6 -mt-5 px-6 pt-5 sm:-mx-8 sm:-mt-7 sm:px-8 sm:pt-7",
528
+ forcedTab: subSlug && hasResourcesTab ? TAB.resources : void 0,
529
+ requestTab: composeRequests > 0 ? TAB.notes : void 0,
530
+ requestNonce: composeRequests
531
+ }
532
+ ) }),
541
533
  /* @__PURE__ */ jsx(
542
534
  ScreenshotGallery,
543
535
  {