@igstack/app-catalog-frontend-core 6.10.3 → 6.12.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 (201) hide show
  1. package/dist/esm/__tests__/modules/auth/AuthContext.test.d.ts +1 -0
  2. package/dist/esm/__tests__/modules/communityTools/likedBy.test.d.ts +1 -0
  3. package/dist/esm/__tests__/modules/communityTools/timeAgo.test.d.ts +1 -0
  4. package/dist/esm/__tests__/modules/communityTools/useTimeUp.test.d.ts +1 -0
  5. package/dist/esm/api/infra/trpc.d.ts +419 -0
  6. package/dist/esm/api/infra/trpc.js.map +1 -1
  7. package/dist/esm/modules/appCatalog/hooks/useUpdateApp.d.ts +1 -1
  8. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.d.ts +2 -4
  9. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js +30 -101
  10. package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js.map +1 -1
  11. package/dist/esm/modules/appCatalog/ui/catalog/DetailDialogShell.d.ts +26 -0
  12. package/dist/esm/modules/appCatalog/ui/catalog/DetailDialogShell.js +108 -0
  13. package/dist/esm/modules/appCatalog/ui/catalog/DetailDialogShell.js.map +1 -0
  14. package/dist/esm/modules/appCatalog/ui/components/ScreenshotGallery.d.ts +2 -1
  15. package/dist/esm/modules/appCatalog/ui/components/ScreenshotGallery.js.map +1 -1
  16. package/dist/esm/modules/appCatalog/ui/components/SectionTabs.d.ts +8 -0
  17. package/dist/esm/modules/appCatalog/ui/components/SectionTabs.js +73 -0
  18. package/dist/esm/modules/appCatalog/ui/components/SectionTabs.js.map +1 -0
  19. package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js +20 -2
  20. package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js.map +1 -1
  21. package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.d.ts +3 -2
  22. package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.js +14 -5
  23. package/dist/esm/modules/appCatalog/ui/detail/CommentsFeed.js.map +1 -1
  24. package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.d.ts +2 -1
  25. package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.js +2 -2
  26. package/dist/esm/modules/appCatalog/ui/detail/CommentsHelp.js.map +1 -1
  27. package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.d.ts +3 -2
  28. package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.js +18 -9
  29. package/dist/esm/modules/appCatalog/ui/detail/CommunityInput.js.map +1 -1
  30. package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.d.ts +4 -2
  31. package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.js +10 -4
  32. package/dist/esm/modules/appCatalog/ui/detail/CommunitySection.js.map +1 -1
  33. package/dist/esm/modules/appCatalog/ui/detail/communityDraft.d.ts +12 -0
  34. package/dist/esm/modules/appCatalog/ui/detail/communityDraft.js +4 -0
  35. package/dist/esm/modules/appCatalog/ui/detail/communityDraft.js.map +1 -1
  36. package/dist/esm/modules/appCatalog/ui/detail/composeComments.js +1 -0
  37. package/dist/esm/modules/appCatalog/ui/detail/composeComments.js.map +1 -1
  38. package/dist/esm/modules/appCatalog/ui/detail/useCommunityToolCount.d.ts +6 -0
  39. package/dist/esm/modules/appCatalog/ui/detail/useCommunityToolCount.js +16 -0
  40. package/dist/esm/modules/appCatalog/ui/detail/useCommunityToolCount.js.map +1 -0
  41. package/dist/esm/modules/appCatalog/ui/layout/AppCatalogLayout.js +2 -2
  42. package/dist/esm/modules/appCatalog/ui/layout/AppCatalogLayout.js.map +1 -1
  43. package/dist/esm/modules/auth/AuthContext.js +7 -3
  44. package/dist/esm/modules/auth/AuthContext.js.map +1 -1
  45. package/dist/esm/modules/auth/ui/LoginModal.js +16 -8
  46. package/dist/esm/modules/auth/ui/LoginModal.js.map +1 -1
  47. package/dist/esm/modules/communityTools/likedBy.d.ts +8 -0
  48. package/dist/esm/modules/communityTools/likedBy.js +23 -0
  49. package/dist/esm/modules/communityTools/likedBy.js.map +1 -0
  50. package/dist/esm/modules/communityTools/timeAgo.d.ts +2 -0
  51. package/dist/esm/modules/communityTools/timeAgo.js +18 -0
  52. package/dist/esm/modules/communityTools/timeAgo.js.map +1 -0
  53. package/dist/esm/modules/communityTools/ui/CardScreenshot.d.ts +13 -0
  54. package/dist/esm/modules/communityTools/ui/CardScreenshot.js +112 -0
  55. package/dist/esm/modules/communityTools/ui/CardScreenshot.js.map +1 -0
  56. package/dist/esm/modules/communityTools/ui/CardTitleBlock.d.ts +8 -0
  57. package/dist/esm/modules/communityTools/ui/CardTitleBlock.js +59 -0
  58. package/dist/esm/modules/communityTools/ui/CardTitleBlock.js.map +1 -0
  59. package/dist/esm/modules/communityTools/ui/CommunityPage.d.ts +12 -0
  60. package/dist/esm/modules/communityTools/ui/CommunityPage.js +547 -0
  61. package/dist/esm/modules/communityTools/ui/CommunityPage.js.map +1 -0
  62. package/dist/esm/modules/communityTools/ui/FeedRow.d.ts +41 -0
  63. package/dist/esm/modules/communityTools/ui/FeedRow.js +252 -0
  64. package/dist/esm/modules/communityTools/ui/FeedRow.js.map +1 -0
  65. package/dist/esm/modules/communityTools/ui/FeedSortControl.d.ts +17 -0
  66. package/dist/esm/modules/communityTools/ui/FeedSortControl.js +84 -0
  67. package/dist/esm/modules/communityTools/ui/FeedSortControl.js.map +1 -0
  68. package/dist/esm/modules/communityTools/ui/LifecycleBadge.d.ts +7 -0
  69. package/dist/esm/modules/communityTools/ui/LifecycleBadge.js +50 -0
  70. package/dist/esm/modules/communityTools/ui/LifecycleBadge.js.map +1 -0
  71. package/dist/esm/modules/communityTools/ui/LikeButton.d.ts +9 -0
  72. package/dist/esm/modules/communityTools/ui/LikeButton.js +56 -0
  73. package/dist/esm/modules/communityTools/ui/LikeButton.js.map +1 -0
  74. package/dist/esm/modules/communityTools/ui/MarginPreview.d.ts +18 -0
  75. package/dist/esm/modules/communityTools/ui/MarginPreview.js +115 -0
  76. package/dist/esm/modules/communityTools/ui/MarginPreview.js.map +1 -0
  77. package/dist/esm/modules/communityTools/ui/MyToolsShelf.d.ts +12 -0
  78. package/dist/esm/modules/communityTools/ui/MyToolsShelf.js +88 -0
  79. package/dist/esm/modules/communityTools/ui/MyToolsShelf.js.map +1 -0
  80. package/dist/esm/modules/communityTools/ui/OwnerPicker.d.ts +56 -0
  81. package/dist/esm/modules/communityTools/ui/OwnerPicker.js +245 -0
  82. package/dist/esm/modules/communityTools/ui/OwnerPicker.js.map +1 -0
  83. package/dist/esm/modules/communityTools/ui/PinButton.d.ts +14 -0
  84. package/dist/esm/modules/communityTools/ui/PinButton.js +72 -0
  85. package/dist/esm/modules/communityTools/ui/PinButton.js.map +1 -0
  86. package/dist/esm/modules/communityTools/ui/ShelfTile.d.ts +13 -0
  87. package/dist/esm/modules/communityTools/ui/ShelfTile.js +81 -0
  88. package/dist/esm/modules/communityTools/ui/ShelfTile.js.map +1 -0
  89. package/dist/esm/modules/communityTools/ui/TagPicker.d.ts +21 -0
  90. package/dist/esm/modules/communityTools/ui/TagPicker.js +87 -0
  91. package/dist/esm/modules/communityTools/ui/TagPicker.js.map +1 -0
  92. package/dist/esm/modules/communityTools/ui/TagRow.d.ts +22 -0
  93. package/dist/esm/modules/communityTools/ui/TagRow.js +92 -0
  94. package/dist/esm/modules/communityTools/ui/TagRow.js.map +1 -0
  95. package/dist/esm/modules/communityTools/ui/Toast.d.ts +19 -0
  96. package/dist/esm/modules/communityTools/ui/Toast.js +87 -0
  97. package/dist/esm/modules/communityTools/ui/Toast.js.map +1 -0
  98. package/dist/esm/modules/communityTools/ui/ToolCard.d.ts +22 -0
  99. package/dist/esm/modules/communityTools/ui/ToolCard.js +468 -0
  100. package/dist/esm/modules/communityTools/ui/ToolCard.js.map +1 -0
  101. package/dist/esm/modules/communityTools/ui/ToolFormDialog.d.ts +32 -0
  102. package/dist/esm/modules/communityTools/ui/ToolFormDialog.js +766 -0
  103. package/dist/esm/modules/communityTools/ui/ToolFormDialog.js.map +1 -0
  104. package/dist/esm/modules/communityTools/ui/ToolSearch.d.ts +15 -0
  105. package/dist/esm/modules/communityTools/ui/ToolSearch.js +92 -0
  106. package/dist/esm/modules/communityTools/ui/ToolSearch.js.map +1 -0
  107. package/dist/esm/modules/communityTools/useCommunityTools.d.ts +159 -0
  108. package/dist/esm/modules/communityTools/useCommunityTools.js +123 -0
  109. package/dist/esm/modules/communityTools/useCommunityTools.js.map +1 -0
  110. package/dist/esm/modules/communityTools/useCommunityToolsConfig.d.ts +10 -0
  111. package/dist/esm/modules/communityTools/useCommunityToolsConfig.js +20 -0
  112. package/dist/esm/modules/communityTools/useCommunityToolsConfig.js.map +1 -0
  113. package/dist/esm/modules/communityTools/useTimeUp.d.ts +5 -0
  114. package/dist/esm/modules/communityTools/useTimeUp.js +13 -0
  115. package/dist/esm/modules/communityTools/useTimeUp.js.map +1 -0
  116. package/dist/esm/node_modules/.pnpm/zod@4.3.5/node_modules/zod/v4/classic/schemas.js +2 -2
  117. package/dist/esm/node_modules/.pnpm/zod@4.3.5/node_modules/zod/v4/core/schemas.js +1 -1
  118. package/dist/esm/routeTree.gen.d.ts +96 -3
  119. package/dist/esm/routeTree.gen.js +19 -0
  120. package/dist/esm/routeTree.gen.js.map +1 -1
  121. package/dist/esm/routes/_layout/community.$slug.d.ts +22 -0
  122. package/dist/esm/routes/_layout/community._slug.js +10 -0
  123. package/dist/esm/routes/_layout/community._slug.js.map +1 -0
  124. package/dist/esm/routes/_layout/community.d.ts +23 -0
  125. package/dist/esm/routes/_layout/community.js +71 -0
  126. package/dist/esm/routes/_layout/community.js.map +1 -0
  127. package/dist/esm/ui/components/header/Header.js +7 -3
  128. package/dist/esm/ui/components/header/Header.js.map +1 -1
  129. package/dist/esm/util/highlightMatches.d.ts +2 -0
  130. package/dist/esm/util/highlightMatches.js +32 -0
  131. package/dist/esm/util/highlightMatches.js.map +1 -0
  132. package/dist/index.css +6 -0
  133. package/package.json +5 -5
  134. package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.appSwitch.test.tsx +3 -0
  135. package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.enter.test.tsx +3 -0
  136. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.background.test.tsx +3 -0
  137. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.behindThisCard.test.tsx +4 -0
  138. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.doorAndTabs.test.tsx +3 -0
  139. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.escape.test.tsx +3 -0
  140. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.knowMore.test.tsx +3 -0
  141. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.sectionOrder.test.tsx +3 -0
  142. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.subResourceTab.test.tsx +3 -0
  143. package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.tags.test.tsx +3 -0
  144. package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.feed.test.tsx +10 -3
  145. package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.help.test.tsx +4 -1
  146. package/src/__tests__/modules/appCatalog/ui/detail/CommunitySection.input.test.tsx +12 -2
  147. package/src/__tests__/modules/appCatalog/ui/detail/composeComments.test.ts +1 -0
  148. package/src/__tests__/modules/auth/AuthContext.test.tsx +55 -0
  149. package/src/__tests__/modules/communityTools/likedBy.test.ts +45 -0
  150. package/src/__tests__/modules/communityTools/timeAgo.test.ts +28 -0
  151. package/src/__tests__/modules/communityTools/useTimeUp.test.ts +31 -0
  152. package/src/__tests__/ui/components/header/Header.login.test.tsx +4 -4
  153. package/src/api/infra/trpc.ts +2 -0
  154. package/src/index.css +6 -0
  155. package/src/modules/appCatalog/ui/catalog/AppDetailPanel.tsx +33 -176
  156. package/src/modules/appCatalog/ui/catalog/DetailDialogShell.tsx +199 -0
  157. package/src/modules/appCatalog/ui/components/ScreenshotGallery.tsx +2 -1
  158. package/src/modules/appCatalog/ui/components/SectionTabs.tsx +81 -0
  159. package/src/modules/appCatalog/ui/detail/AppDetails.tsx +17 -2
  160. package/src/modules/appCatalog/ui/detail/CommentsFeed.tsx +20 -5
  161. package/src/modules/appCatalog/ui/detail/CommentsHelp.tsx +6 -3
  162. package/src/modules/appCatalog/ui/detail/CommunityInput.tsx +21 -10
  163. package/src/modules/appCatalog/ui/detail/CommunitySection.tsx +10 -5
  164. package/src/modules/appCatalog/ui/detail/communityDraft.ts +16 -0
  165. package/src/modules/appCatalog/ui/detail/composeComments.ts +1 -0
  166. package/src/modules/appCatalog/ui/detail/useCommunityToolCount.ts +18 -0
  167. package/src/modules/appCatalog/ui/layout/AppCatalogLayout.tsx +2 -2
  168. package/src/modules/auth/AuthContext.tsx +9 -3
  169. package/src/modules/auth/ui/LoginModal.tsx +7 -2
  170. package/src/modules/communityTools/likedBy.ts +32 -0
  171. package/src/modules/communityTools/timeAgo.ts +16 -0
  172. package/src/modules/communityTools/ui/CardScreenshot.tsx +124 -0
  173. package/src/modules/communityTools/ui/CardTitleBlock.tsx +58 -0
  174. package/src/modules/communityTools/ui/CommunityPage.tsx +626 -0
  175. package/src/modules/communityTools/ui/FeedRow.tsx +303 -0
  176. package/src/modules/communityTools/ui/FeedSortControl.tsx +97 -0
  177. package/src/modules/communityTools/ui/LifecycleBadge.tsx +44 -0
  178. package/src/modules/communityTools/ui/LikeButton.tsx +56 -0
  179. package/src/modules/communityTools/ui/MarginPreview.tsx +143 -0
  180. package/src/modules/communityTools/ui/MyToolsShelf.tsx +112 -0
  181. package/src/modules/communityTools/ui/OwnerPicker.tsx +311 -0
  182. package/src/modules/communityTools/ui/PinButton.tsx +87 -0
  183. package/src/modules/communityTools/ui/ShelfTile.tsx +83 -0
  184. package/src/modules/communityTools/ui/TagPicker.tsx +118 -0
  185. package/src/modules/communityTools/ui/TagRow.tsx +118 -0
  186. package/src/modules/communityTools/ui/Toast.tsx +105 -0
  187. package/src/modules/communityTools/ui/ToolCard.tsx +519 -0
  188. package/src/modules/communityTools/ui/ToolFormDialog.tsx +922 -0
  189. package/src/modules/communityTools/ui/ToolSearch.tsx +111 -0
  190. package/src/modules/communityTools/useCommunityTools.ts +146 -0
  191. package/src/modules/communityTools/useCommunityToolsConfig.ts +26 -0
  192. package/src/modules/communityTools/useTimeUp.ts +14 -0
  193. package/src/routeTree.gen.ts +52 -0
  194. package/src/routes/_layout/community.$slug.tsx +7 -0
  195. package/src/routes/_layout/community.tsx +97 -0
  196. package/src/ui/components/header/Header.tsx +9 -3
  197. package/src/util/highlightMatches.tsx +34 -0
  198. package/dist/esm/modules/appCatalog/ui/components/ViewToggle.d.ts +0 -12
  199. package/dist/esm/modules/appCatalog/ui/components/ViewToggle.js +0 -56
  200. package/dist/esm/modules/appCatalog/ui/components/ViewToggle.js.map +0 -1
  201. package/src/modules/appCatalog/ui/components/ViewToggle.tsx +0 -59
@@ -0,0 +1,32 @@
1
+ import { jsx, Fragment } from "react/jsx-runtime";
2
+ import React__default from "react";
3
+ function escapeRegex(str) {
4
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+ function highlightMatches(text, query) {
7
+ if (!query) return text;
8
+ const safeQuery = escapeRegex(query);
9
+ const regex = new RegExp(`(${safeQuery})`, "gi");
10
+ const parts = text.split(regex);
11
+ return /* @__PURE__ */ jsx(Fragment, { children: parts.map((part, idx) => {
12
+ const key = `${part}-${text.indexOf(part, idx)}`;
13
+ return idx % 2 === 1 ? /* @__PURE__ */ jsx("span", { className: "bg-highlight", children: part }, key) : /* @__PURE__ */ jsx(React__default.Fragment, { children: part }, key);
14
+ }) });
15
+ }
16
+ function highlightTokens(text, tokens) {
17
+ const words = tokens.filter(Boolean);
18
+ if (words.length === 0) return text;
19
+ const alternation = [...words].sort((a, b) => b.length - a.length).map(escapeRegex).join("|");
20
+ const parts = text.split(new RegExp(`(${alternation})`, "gi"));
21
+ let offset = 0;
22
+ return /* @__PURE__ */ jsx(Fragment, { children: parts.map((part, idx) => {
23
+ const key = `${offset}-${idx % 2}`;
24
+ offset += part.length;
25
+ return idx % 2 === 1 ? /* @__PURE__ */ jsx("span", { className: "rounded-[3px] bg-highlight", children: part }, key) : /* @__PURE__ */ jsx(React__default.Fragment, { children: part }, key);
26
+ }) });
27
+ }
28
+ export {
29
+ highlightMatches,
30
+ highlightTokens
31
+ };
32
+ //# sourceMappingURL=highlightMatches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlightMatches.js","sources":["../../../src/util/highlightMatches.tsx"],"sourcesContent":["import React from 'react'\n\n// Escape regex special characters\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nexport function highlightMatches(text: string, query: string): React.ReactNode {\n if (!query) return text\n\n const safeQuery = escapeRegex(query)\n const regex = new RegExp(`(${safeQuery})`, 'gi')\n const parts = text.split(regex)\n\n return (\n <>\n {parts.map((part, idx) => {\n const key = `${part}-${text.indexOf(part, idx)}`\n return idx % 2 === 1 ? (\n <span key={key} className=\"bg-highlight\">\n {part}\n </span>\n ) : (\n <React.Fragment key={key}>{part}</React.Fragment>\n )\n })}\n </>\n )\n}\n\n/** Like `highlightMatches`, for several search words at once, in any order. */\nexport function highlightTokens(\n text: string,\n tokens: string[],\n): React.ReactNode {\n const words = tokens.filter(Boolean)\n if (words.length === 0) return text\n\n // Longest first, so \"runbook\" wins over its own prefix \"run\".\n const alternation = [...words]\n .sort((a, b) => b.length - a.length)\n .map(escapeRegex)\n .join('|')\n const parts = text.split(new RegExp(`(${alternation})`, 'gi'))\n\n let offset = 0\n return (\n <>\n {parts.map((part, idx) => {\n // An empty part shares its offset with the match after it; parity splits them.\n const key = `${offset}-${idx % 2}`\n offset += part.length\n return idx % 2 === 1 ? (\n <span key={key} className=\"rounded-[3px] bg-highlight\">\n {part}\n </span>\n ) : (\n <React.Fragment key={key}>{part}</React.Fragment>\n )\n })}\n </>\n )\n}\n"],"names":["React"],"mappings":";;AAGA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,uBAAuB,MAAM;AAClD;AAEO,SAAS,iBAAiB,MAAc,OAAgC;AAC7E,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,YAAY,YAAY,KAAK;AACnC,QAAM,QAAQ,IAAI,OAAO,IAAI,SAAS,KAAK,IAAI;AAC/C,QAAM,QAAQ,KAAK,MAAM,KAAK;AAE9B,SACE,oBAAA,UAAA,EACG,UAAA,MAAM,IAAI,CAAC,MAAM,QAAQ;AACxB,UAAM,MAAM,GAAG,IAAI,IAAI,KAAK,QAAQ,MAAM,GAAG,CAAC;AAC9C,WAAO,MAAM,MAAM,IACjB,oBAAC,UAAe,WAAU,gBACvB,UAAA,KAAA,GADQ,GAEX,IAEA,oBAACA,eAAM,UAAN,EAA0B,kBAAN,GAAW;AAAA,EAEpC,CAAC,EAAA,CACH;AAEJ;AAGO,SAAS,gBACd,MACA,QACiB;AACjB,QAAM,QAAQ,OAAO,OAAO,OAAO;AACnC,MAAI,MAAM,WAAW,EAAG,QAAO;AAG/B,QAAM,cAAc,CAAC,GAAG,KAAK,EAC1B,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,IAAI,WAAW,EACf,KAAK,GAAG;AACX,QAAM,QAAQ,KAAK,MAAM,IAAI,OAAO,IAAI,WAAW,KAAK,IAAI,CAAC;AAE7D,MAAI,SAAS;AACb,SACE,oBAAA,UAAA,EACG,UAAA,MAAM,IAAI,CAAC,MAAM,QAAQ;AAExB,UAAM,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC;AAChC,cAAU,KAAK;AACf,WAAO,MAAM,MAAM,IACjB,oBAAC,UAAe,WAAU,8BACvB,UAAA,KAAA,GADQ,GAEX,IAEA,oBAACA,eAAM,UAAN,EAA0B,kBAAN,GAAW;AAAA,EAEpC,CAAC,EAAA,CACH;AAEJ;"}
package/dist/index.css CHANGED
@@ -33,6 +33,7 @@
33
33
  --muted-foreground: oklch(0.52 0.025 60);
34
34
  --accent: oklch(0.93 0.05 70); /* soft amber accent */
35
35
  --accent-foreground: oklch(0.36 0.04 55);
36
+ --highlight: oklch(0.88 0.11 80); /* search-match marker */
36
37
  --destructive: oklch(0.6368 0.2078 25.3313);
37
38
  --destructive-foreground: oklch(1 0 0);
38
39
  --border: oklch(0.9 0.018 80); /* warm hairline */
@@ -714,6 +715,11 @@
714
715
  font-style: italic;
715
716
  color: var(--muted-foreground);
716
717
  }
718
+ /* A line the system wrote when a tool changed: context, not someone's note. */
719
+ .comment__text.is-system {
720
+ font-size: 0.84375rem;
721
+ color: var(--muted-foreground);
722
+ }
717
723
  .comment__reply {
718
724
  margin-top: 0.25rem;
719
725
  font-size: 0.84375rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igstack/app-catalog-frontend-core",
3
- "version": "6.10.3",
3
+ "version": "6.12.0",
4
4
  "description": "Frontend core library for App Catalog",
5
5
  "homepage": "https://github.com/lislon/app-catalog",
6
6
  "repository": {
@@ -73,7 +73,7 @@
73
73
  "@tanstack/react-query": "^5.90.12",
74
74
  "@tanstack/react-query-devtools": "^5.91.1",
75
75
  "@tanstack/react-table": "^8.21.3",
76
- "@tanstack/react-virtual": "3.13.13",
76
+ "@tanstack/react-virtual": "3.14.21",
77
77
  "@tanstack/router-plugin": "1.168.42",
78
78
  "@tiptap/extension-link": "^2.10.0",
79
79
  "@tiptap/extension-placeholder": "^2.10.0",
@@ -118,7 +118,7 @@
118
118
  "tailwindcss": "^4.1.7",
119
119
  "tiptap-markdown": "^0.8.0",
120
120
  "tslib": "^2.3.0",
121
- "@igstack/app-catalog-shared-core": "6.10.3"
121
+ "@igstack/app-catalog-shared-core": "6.12.0"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@tailwindcss/vite": "^4.0.5",
@@ -146,7 +146,7 @@
146
146
  "vite-plugin-static-copy": "^3.1.4",
147
147
  "vite-plugin-svgr": "^4.2.0",
148
148
  "vitest": "^4.1.2",
149
- "@igstack/app-catalog-backend-core": "6.10.3"
149
+ "@igstack/app-catalog-backend-core": "6.12.0"
150
150
  },
151
151
  "peerDependencies": {
152
152
  "@tanstack/react-router": "1.170.41",
@@ -156,7 +156,7 @@
156
156
  "vite": "^6.3.5",
157
157
  "vite-plugin-svgr": "^4.2.0"
158
158
  },
159
- "gitHead": "980bbf4c4901132d351291df3522fa6b6eaaa123",
159
+ "gitHead": "94ade32ef879b41d8c62f2121a41e4b35f46b2ee",
160
160
  "scripts": {
161
161
  "build": "vite build",
162
162
  "build:lenient": "vite build --mode lenient",
@@ -23,6 +23,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
23
23
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
24
24
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
25
25
  }))
26
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
27
+ useCommunityToolCount: vi.fn(() => 0),
28
+ }))
26
29
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
27
30
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
28
31
  }))
@@ -20,6 +20,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
20
20
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
21
21
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
22
22
  }))
23
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
24
+ useCommunityToolCount: vi.fn(() => 0),
25
+ }))
23
26
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
24
27
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
25
28
  }))
@@ -21,6 +21,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
21
21
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
22
22
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
23
23
  }))
24
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
25
+ useCommunityToolCount: vi.fn(() => 0),
26
+ }))
24
27
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
25
28
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
26
29
  }))
@@ -28,6 +28,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
28
28
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
29
29
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
30
30
  }))
31
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
32
+ useCommunityToolCount: vi.fn(() => 0),
33
+ }))
31
34
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
32
35
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
33
36
  }))
@@ -283,6 +286,7 @@ const pendingNote = {
283
286
  reviewedAt: null,
284
287
  editedAt: null,
285
288
  isMine: false,
289
+ isSystem: false,
286
290
  canEditUntil: null,
287
291
  status: null,
288
292
  reviewerReply: null,
@@ -26,6 +26,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
26
26
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
27
27
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
28
28
  }))
29
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
30
+ useCommunityToolCount: vi.fn(() => 0),
31
+ }))
29
32
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
30
33
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
31
34
  }))
@@ -22,6 +22,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
22
22
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
23
23
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
24
24
  }))
25
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
26
+ useCommunityToolCount: vi.fn(() => 0),
27
+ }))
25
28
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
26
29
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
27
30
  }))
@@ -22,6 +22,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
22
22
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
23
23
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
24
24
  }))
25
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
26
+ useCommunityToolCount: vi.fn(() => 0),
27
+ }))
25
28
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
26
29
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
27
30
  }))
@@ -23,6 +23,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
23
23
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
24
24
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
25
25
  }))
26
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
27
+ useCommunityToolCount: vi.fn(() => 0),
28
+ }))
26
29
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
27
30
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
28
31
  }))
@@ -56,6 +56,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
56
56
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
57
57
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
58
58
  }))
59
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
60
+ useCommunityToolCount: vi.fn(() => 0),
61
+ }))
59
62
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
60
63
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
61
64
  }))
@@ -21,6 +21,9 @@ vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
21
21
  vi.mock('~/modules/appCatalog/hooks/useAppClickHistory', () => ({
22
22
  useAppClickHistory: vi.fn(() => ({ recordClick: vi.fn() })),
23
23
  }))
24
+ vi.mock('~/modules/appCatalog/ui/detail/useCommunityToolCount', () => ({
25
+ useCommunityToolCount: vi.fn(() => 0),
26
+ }))
24
27
  vi.mock('~/modules/appCatalog/hooks/useUpdateApp', () => ({
25
28
  useUpdateApp: vi.fn(() => ({ mutate: vi.fn() })),
26
29
  }))
@@ -50,6 +50,7 @@ const note = {
50
50
  reviewedAt: null as string | null,
51
51
  editedAt: null,
52
52
  isMine: false,
53
+ isSystem: false,
53
54
  canEditUntil: null as string | null,
54
55
  status: null as string | null,
55
56
  reviewerReply: null as string | null,
@@ -58,7 +59,7 @@ const note = {
58
59
  function mount(claims: Claim[] = []) {
59
60
  render(
60
61
  <CommunitySection
61
- appSlug="example-app"
62
+ target={{ resourceSlug: 'example-app' }}
62
63
  appName="Example App"
63
64
  claims={claims}
64
65
  />,
@@ -190,7 +191,10 @@ describe('CommunitySection — the Comments feed', () => {
190
191
  it('offers Dismiss on your own open note, and not once it is answered', () => {
191
192
  state.items = [{ ...note, isMine: true }]
192
193
  const { unmount } = render(
193
- <CommunitySection appSlug="example-app" appName="Example App" />,
194
+ <CommunitySection
195
+ target={{ resourceSlug: 'example-app' }}
196
+ appName="Example App"
197
+ />,
194
198
  )
195
199
  expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument()
196
200
  expect(screen.queryByRole('button', { name: 'Edit' })).toBeNull()
@@ -262,7 +266,10 @@ describe('CommunitySection — the Comments feed', () => {
262
266
  isPending: true,
263
267
  } as never)
264
268
  const { unmount } = render(
265
- <CommunitySection appSlug="example-app" appName="Example App" />,
269
+ <CommunitySection
270
+ target={{ resourceSlug: 'example-app' }}
271
+ appName="Example App"
272
+ />,
266
273
  )
267
274
  expect(screen.getByText('Loading comments…')).toBeInTheDocument()
268
275
  unmount()
@@ -52,7 +52,10 @@ function mount(plugins: AcPlugin[] | undefined) {
52
52
  return render(
53
53
  <ExtensionsContext value={plugins}>
54
54
  <Card onClose={onCardClose}>
55
- <CommunitySection appSlug="test-app" appName="Test App" />
55
+ <CommunitySection
56
+ target={{ resourceSlug: 'test-app' }}
57
+ appName="Test App"
58
+ />
56
59
  </Card>
57
60
  </ExtensionsContext>,
58
61
  )
@@ -85,7 +85,12 @@ afterEach(() => {
85
85
  })
86
86
 
87
87
  const mount = () =>
88
- render(<CommunitySection appSlug="team-board" appName="Team Board" />)
88
+ render(
89
+ <CommunitySection
90
+ target={{ resourceSlug: 'team-board' }}
91
+ appName="Team Board"
92
+ />,
93
+ )
89
94
  const field = () =>
90
95
  screen.getByRole('textbox', { name: 'Know more about Team Board?' })
91
96
  const send = () => screen.getByText('Send')
@@ -349,7 +354,12 @@ describe('CommunitySection — screenshots', () => {
349
354
  await userEvent.type(field(), 'about this one')
350
355
  first.unmount()
351
356
 
352
- render(<CommunitySection appSlug="other-app" appName="Other" />)
357
+ render(
358
+ <CommunitySection
359
+ target={{ resourceSlug: 'other-app' }}
360
+ appName="Other"
361
+ />,
362
+ )
353
363
  expect(
354
364
  screen.getByRole('textbox', { name: 'Know more about Other?' }),
355
365
  ).toHaveValue('')
@@ -17,6 +17,7 @@ const note = (over: Partial<FeedbackView> = {}): FeedbackView => ({
17
17
  reviewedAt: null,
18
18
  editedAt: null,
19
19
  isMine: false,
20
+ isSystem: false,
20
21
  canEditUntil: null,
21
22
  status: null,
22
23
  reviewerReply: null,
@@ -0,0 +1,55 @@
1
+ import { renderHook, waitFor } from '@testing-library/react'
2
+ import { afterEach, describe, expect, it, vi } from 'vitest'
3
+ import { AuthProvider, useAuth } from '~/modules/auth/AuthContext'
4
+
5
+ // The session endpoint answers `{ ...session, isAdmin }`: admin rights sit
6
+ // beside the user, not on it.
7
+ function serveSession(body: unknown) {
8
+ vi.stubGlobal(
9
+ 'fetch',
10
+ vi.fn().mockResolvedValue({ ok: true, json: async () => body }),
11
+ )
12
+ }
13
+
14
+ function renderAuth() {
15
+ return renderHook(() => useAuth(), {
16
+ wrapper: ({ children }) => <AuthProvider>{children}</AuthProvider>,
17
+ })
18
+ }
19
+
20
+ describe('AuthProvider', () => {
21
+ afterEach(() => {
22
+ vi.unstubAllGlobals()
23
+ localStorage.clear()
24
+ })
25
+
26
+ it('session with top-level isAdmin yields user.isAdmin true', async () => {
27
+ serveSession({ user: { id: 'u1', name: 'Ada Admin' }, isAdmin: true })
28
+
29
+ const { result } = renderAuth()
30
+
31
+ await waitFor(() => expect(result.current.isLoading).toBe(false))
32
+ expect(result.current.user).toEqual({
33
+ id: 'u1',
34
+ name: 'Ada Admin',
35
+ isAdmin: true,
36
+ })
37
+ // The optimistic copy the next load renders from keeps it too.
38
+ expect(JSON.parse(localStorage.getItem('ac_auth_user') ?? 'null')).toEqual(
39
+ result.current.user,
40
+ )
41
+ })
42
+
43
+ it('session without isAdmin yields user.isAdmin false', async () => {
44
+ serveSession({ user: { id: 'u2', name: 'Robin Reader' } })
45
+
46
+ const { result } = renderAuth()
47
+
48
+ await waitFor(() => expect(result.current.isLoading).toBe(false))
49
+ expect(result.current.user).toEqual({
50
+ id: 'u2',
51
+ name: 'Robin Reader',
52
+ isAdmin: false,
53
+ })
54
+ })
55
+ })
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { CommunityTool } from '@igstack/app-catalog-shared-core'
3
+ import { likedByText, withLike } from '~/modules/communityTools/likedBy'
4
+
5
+ describe('likedByText', () => {
6
+ const text = (likes: number, likedByMe: boolean, likedBy: string[]) =>
7
+ likedByText({ likes, likedByMe, likedBy })
8
+ const nine = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
9
+
10
+ it('names colleagues, the last after "and"', () => {
11
+ expect(text(0, false, [])).toBe('No likes yet')
12
+ expect(text(1, false, ['Ann'])).toBe('Liked by Ann')
13
+ expect(text(3, false, ['Ann', 'Bob', 'Cy'])).toBe(
14
+ 'Liked by Ann, Bob and Cy',
15
+ )
16
+ })
17
+
18
+ it('puts you first', () => {
19
+ expect(text(1, true, [])).toBe('Liked by you')
20
+ expect(text(2, true, ['Ann'])).toBe('Liked by you and Ann')
21
+ })
22
+
23
+ it('names eight colleagues at most, and counts the rest', () => {
24
+ expect(text(13, true, nine)).toBe(
25
+ 'Liked by you, A, B, C, D, E, F, G, H and 4 others',
26
+ )
27
+ expect(text(9, false, nine)).toBe(
28
+ 'Liked by A, B, C, D, E, F, G, H and 1 other',
29
+ )
30
+ })
31
+ })
32
+
33
+ describe('withLike', () => {
34
+ const tool = { likes: 2, likedByMe: false, likedBy: ['Bob'] } as CommunityTool
35
+
36
+ it('counts my like in, and out again', () => {
37
+ const liked = withLike(tool, true)
38
+ expect(liked).toMatchObject({ likes: 3, likedByMe: true, likedBy: ['Bob'] })
39
+ expect(withLike(liked, false)).toMatchObject(tool)
40
+ })
41
+
42
+ it('leaves a tool that already reads that way alone', () => {
43
+ expect(withLike(tool, false)).toBe(tool)
44
+ })
45
+ })
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { timeAgo } from '~/modules/communityTools/timeAgo'
3
+
4
+ describe('timeAgo', () => {
5
+ const now = new Date(2026, 9, 21, 9, 0)
6
+ const before = (days: number) =>
7
+ new Date(2026, 9, 21 - days, 23, 30).toISOString()
8
+
9
+ it('counts calendar days, then months, then years', () => {
10
+ expect(
11
+ [0, 1, 3, 30, 31, 61, 364, 365, 800].map((d) => timeAgo(before(d), now)),
12
+ ).toEqual([
13
+ 'today',
14
+ 'yesterday',
15
+ '3 days ago',
16
+ '30 days ago',
17
+ '1 month ago',
18
+ '2 months ago',
19
+ '12 months ago',
20
+ '1 year ago',
21
+ '2 years ago',
22
+ ])
23
+ })
24
+
25
+ it('reads a time in the future as today', () => {
26
+ expect(timeAgo(new Date(2026, 9, 22).toISOString(), now)).toBe('today')
27
+ })
28
+ })
@@ -0,0 +1,31 @@
1
+ import { act, renderHook } from '@testing-library/react'
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { useTimeUp } from '~/modules/communityTools/useTimeUp'
4
+
5
+ const TWO_MINUTES = 2 * 60 * 1000
6
+
7
+ describe('useTimeUp', () => {
8
+ beforeEach(() => vi.useFakeTimers())
9
+ afterEach(() => vi.useRealTimers())
10
+
11
+ it('turns true at the limit and not a moment before', () => {
12
+ const { result } = renderHook(() => useTimeUp('fresh-tool', TWO_MINUTES))
13
+ act(() => vi.advanceTimersByTime(TWO_MINUTES - 1))
14
+ expect(result.current).toBe(false)
15
+ act(() => vi.advanceTimersByTime(1))
16
+ expect(result.current).toBe(true)
17
+ })
18
+
19
+ it('starts again for a new key', () => {
20
+ const { result, rerender } = renderHook(
21
+ ({ key }) => useTimeUp(key, TWO_MINUTES),
22
+ { initialProps: { key: 'fresh-tool' } },
23
+ )
24
+ act(() => vi.advanceTimersByTime(TWO_MINUTES))
25
+ expect(result.current).toBe(true)
26
+ rerender({ key: 'other-tool' })
27
+ expect(result.current).toBe(false)
28
+ act(() => vi.advanceTimersByTime(TWO_MINUTES))
29
+ expect(result.current).toBe(true)
30
+ })
31
+ })
@@ -54,11 +54,11 @@ beforeEach(() => {
54
54
  })
55
55
 
56
56
  describe('Header login button', () => {
57
- it('offers an anonymous visitor a Login button that opens the sign-in modal', async () => {
57
+ it('offers an anonymous visitor a Sign in button that opens the sign-in modal', async () => {
58
58
  window.history.pushState({}, '', '/app/sample-review-app?tab=access')
59
59
  render(<Header />)
60
60
 
61
- await userEvent.click(screen.getByRole('button', { name: 'Login' }))
61
+ await userEvent.click(screen.getByRole('button', { name: 'Sign in' }))
62
62
 
63
63
  // Comes back to where the visitor was after signing in.
64
64
  expect(openLoginModal).toHaveBeenCalledWith(
@@ -75,7 +75,7 @@ describe('Header login button', () => {
75
75
  expect(
76
76
  screen.getByRole('button', { name: 'DEV Login' }),
77
77
  ).toBeInTheDocument()
78
- expect(screen.getByRole('button', { name: 'Login' })).toBeInTheDocument()
78
+ expect(screen.getByRole('button', { name: 'Sign in' })).toBeInTheDocument()
79
79
  })
80
80
 
81
81
  it('leaves no empty flex child in the header row', () => {
@@ -95,6 +95,6 @@ describe('Header login button', () => {
95
95
  render(<Header />)
96
96
 
97
97
  expect(screen.getByTestId('user-avatar-button')).toBeInTheDocument()
98
- expect(screen.queryByRole('button', { name: 'Login' })).toBeNull()
98
+ expect(screen.queryByRole('button', { name: 'Sign in' })).toBeNull()
99
99
  })
100
100
  })
@@ -3,4 +3,6 @@ import type { TRPCRouter } from '@igstack/app-catalog-backend-core'
3
3
 
4
4
  const { TRPCProvider, useTRPC, useTRPCClient } = createTRPCContext<TRPCRouter>()
5
5
 
6
+ // Re-export with explicit types to avoid portable type errors
6
7
  export { TRPCProvider, useTRPC, useTRPCClient }
8
+ export type { TRPCRouter }
package/src/index.css CHANGED
@@ -33,6 +33,7 @@
33
33
  --muted-foreground: oklch(0.52 0.025 60);
34
34
  --accent: oklch(0.93 0.05 70); /* soft amber accent */
35
35
  --accent-foreground: oklch(0.36 0.04 55);
36
+ --highlight: oklch(0.88 0.11 80); /* search-match marker */
36
37
  --destructive: oklch(0.6368 0.2078 25.3313);
37
38
  --destructive-foreground: oklch(1 0 0);
38
39
  --border: oklch(0.9 0.018 80); /* warm hairline */
@@ -714,6 +715,11 @@
714
715
  font-style: italic;
715
716
  color: var(--muted-foreground);
716
717
  }
718
+ /* A line the system wrote when a tool changed: context, not someone's note. */
719
+ .comment__text.is-system {
720
+ font-size: 0.84375rem;
721
+ color: var(--muted-foreground);
722
+ }
717
723
  .comment__reply {
718
724
  margin-top: 0.25rem;
719
725
  font-size: 0.84375rem;