@flamingo-stack/openframe-frontend-core 0.0.770 → 0.0.772

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 (168) hide show
  1. package/dist/build/export-map.json +1 -1
  2. package/dist/{chunk-C66DWLCR.cjs → chunk-2MAENZL3.cjs} +37 -37
  3. package/dist/{chunk-2MND64QJ.cjs → chunk-5HBGBLJT.cjs} +50 -63
  4. package/dist/{chunk-4EJU462O.cjs → chunk-5V7BIUGA.cjs} +2 -2
  5. package/dist/{chunk-HK6UQVOI.cjs → chunk-7FYOVKQD.cjs} +12 -12
  6. package/dist/{chunk-MYA6NR2C.cjs → chunk-CTRJNTYU.cjs} +2 -2
  7. package/dist/{chunk-S5EUREOB.cjs → chunk-E25TJPWV.cjs} +3 -3
  8. package/dist/{chunk-HQF62LAU.cjs → chunk-FPCGKH5K.cjs} +28 -28
  9. package/dist/{chunk-VATCQOVC.cjs → chunk-FT5NPAZY.cjs} +17 -17
  10. package/dist/{chunk-I2TSCJT2.cjs → chunk-HFNWBNLE.cjs} +4 -4
  11. package/dist/{chunk-MFRIFVDV.cjs → chunk-IHCHU355.cjs} +41 -41
  12. package/dist/{chunk-NZELWPKO.cjs → chunk-ILZEXCJH.cjs} +98 -98
  13. package/dist/{chunk-DCBZMWLY.cjs → chunk-J5ZNEAGY.cjs} +24 -24
  14. package/dist/{chunk-BLTNKLAX.cjs → chunk-JJNOAYD4.cjs} +4 -4
  15. package/dist/{chunk-QS4G7CHU.cjs → chunk-KSCEAIBS.cjs} +2 -2
  16. package/dist/{chunk-Q575DR36.cjs → chunk-QOEOY7BJ.cjs} +33 -33
  17. package/dist/{chunk-SL7KFZWJ.cjs → chunk-QPK2433W.cjs} +24 -24
  18. package/dist/{chunk-VL44GB2I.cjs → chunk-REFL3YNE.cjs} +13 -13
  19. package/dist/{chunk-37XVY2RE.cjs → chunk-S3NEBAKD.cjs} +6 -6
  20. package/dist/{chunk-TBKXXMBU.cjs → chunk-UR3I3J5K.cjs} +90 -86
  21. package/dist/{chunk-MZLWB5AH.cjs → chunk-V3W56L2D.cjs} +18 -9
  22. package/dist/{chunk-4BRO3RZ6.cjs → chunk-VZAOLKKF.cjs} +72 -80
  23. package/dist/{chunk-QMUKTHWM.cjs → chunk-W5LX7DFB.cjs} +3 -3
  24. package/dist/{chunk-ZVNL5OK6.cjs → chunk-X5Z4HRH7.cjs} +2 -2
  25. package/dist/{chunk-WUYL4BMD.cjs → chunk-YI3CDGKM.cjs} +70 -70
  26. package/dist/{chunk-BZLHU4EK.cjs → chunk-ZVKJKWAB.cjs} +713 -695
  27. package/dist/{chunk-QSLKTDZ4.cjs → chunk-ZYGC2A7G.cjs} +4 -4
  28. package/dist/{cloud-tenants-IAZQL2JF.cjs → cloud-tenants-CUNC5KBD.cjs} +8 -8
  29. package/dist/components/case-studies/index.cjs +32 -30
  30. package/dist/components/case-studies/quote-cards-strip.d.ts +7 -2
  31. package/dist/components/case-studies/quote-cards-strip.d.ts.map +1 -1
  32. package/dist/components/case-studies/quote-cards-strip.js +7 -5
  33. package/dist/components/categories-cart.d.ts +9 -2
  34. package/dist/components/categories-cart.d.ts.map +1 -1
  35. package/dist/components/categories-cart.js +9 -4
  36. package/dist/components/chat/entity-cards/admin-content-card-grid.d.ts +5 -1
  37. package/dist/components/chat/entity-cards/admin-content-card-grid.d.ts.map +1 -1
  38. package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -1
  39. package/dist/components/chat/entity-cards/program-card.js +2 -2
  40. package/dist/components/chat/index.cjs +5 -5
  41. package/dist/components/contact/index.cjs +9 -9
  42. package/dist/components/docs/index.cjs +12 -12
  43. package/dist/components/embeds/index.cjs +9 -9
  44. package/dist/components/faq/faq-section.d.ts +7 -4
  45. package/dist/components/faq/faq-section.d.ts.map +1 -1
  46. package/dist/components/faq/index.cjs +10 -10
  47. package/dist/components/faq-accordion.d.ts +2 -4
  48. package/dist/components/faq-accordion.d.ts.map +1 -1
  49. package/dist/components/features/capability-explorer.d.ts +8 -2
  50. package/dist/components/features/capability-explorer.d.ts.map +1 -1
  51. package/dist/components/features/capability-explorer.js +7 -3
  52. package/dist/components/features/cards-strip.d.ts +2 -2
  53. package/dist/components/features/index.cjs +8 -8
  54. package/dist/components/features/model-token-rates.js +2 -2
  55. package/dist/components/features/remote-session/remote-desktop-chat-panel.js +2 -2
  56. package/dist/components/help-center-pages/index.cjs +94 -103
  57. package/dist/components/help-center-pages/trust-center-page.d.ts +1 -1
  58. package/dist/components/help-center-pages/trust-center-sections.d.ts +7 -4
  59. package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
  60. package/dist/components/help-center-pages/trust-center-sections.js +5 -14
  61. package/dist/components/index.cjs +66 -61
  62. package/dist/components/logs-list.d.ts.map +1 -1
  63. package/dist/components/logs-list.js +3 -2
  64. package/dist/components/meeting-scheduler/index.cjs +47 -47
  65. package/dist/components/navigation/index.cjs +11 -11
  66. package/dist/components/navigation/multi-level-navigation.d.ts +6 -7
  67. package/dist/components/navigation/multi-level-navigation.d.ts.map +1 -1
  68. package/dist/components/onboarding-guides/index.cjs +11 -11
  69. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts +5 -5
  70. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts.map +1 -1
  71. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.js +3 -3
  72. package/dist/components/product-demo/index.cjs +18 -18
  73. package/dist/components/related-content/index.cjs +5 -5
  74. package/dist/components/related-content/related-content-section.d.ts.map +1 -1
  75. package/dist/components/related-content/related-content-section.js +4 -17
  76. package/dist/components/shared/dev-section/dev-card-row.d.ts +3 -3
  77. package/dist/components/shared/dev-section/dev-card-row.d.ts.map +1 -1
  78. package/dist/components/shared/product-release/release-detail-page.js +2 -2
  79. package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts +6 -1
  80. package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts.map +1 -1
  81. package/dist/components/shared/roadmap/roadmap-grid-skeleton.js +3 -1
  82. package/dist/components/shared/roadmap/roadmap-grid.d.ts +10 -3
  83. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
  84. package/dist/components/shared/roadmap/roadmap-grid.js +6 -3
  85. package/dist/components/tickets/help-center-list.js +4 -4
  86. package/dist/components/tickets/hooks/use-tickets-list.d.ts +2 -0
  87. package/dist/components/tickets/hooks/use-tickets-list.d.ts.map +1 -1
  88. package/dist/components/tickets/hooks/use-tickets-list.js +3 -2
  89. package/dist/components/tickets/index.cjs +14 -14
  90. package/dist/components/tickets/ticket-center.d.ts.map +1 -1
  91. package/dist/components/tickets/ticket-center.js +3 -9
  92. package/dist/components/ui/autocomplete.d.ts +5 -0
  93. package/dist/components/ui/autocomplete.d.ts.map +1 -1
  94. package/dist/components/ui/autocomplete.js +3 -2
  95. package/dist/components/ui/data-table/data-table-body.d.ts +5 -2
  96. package/dist/components/ui/data-table/data-table-body.d.ts.map +1 -1
  97. package/dist/components/ui/data-table/data-table-body.js +3 -1
  98. package/dist/components/ui/data-table/data-table-infinite-footer.d.ts +5 -2
  99. package/dist/components/ui/data-table/data-table-infinite-footer.d.ts.map +1 -1
  100. package/dist/components/ui/data-table/data-table-infinite-footer.js +5 -2
  101. package/dist/components/ui/data-table/utils.d.ts +21 -0
  102. package/dist/components/ui/data-table/utils.d.ts.map +1 -1
  103. package/dist/components/ui/data-table/utils.js +7 -0
  104. package/dist/components/ui/index.cjs +8 -8
  105. package/dist/components/ui/search-input.d.ts +3 -1
  106. package/dist/components/ui/search-input.d.ts.map +1 -1
  107. package/dist/components/ui/search-input.js +3 -2
  108. package/dist/components/ui/searchable-select.d.ts +2 -0
  109. package/dist/components/ui/searchable-select.d.ts.map +1 -1
  110. package/dist/components/ui/searchable-select.js +10 -6
  111. package/dist/components/ui/select.d.ts +11 -1
  112. package/dist/components/ui/select.d.ts.map +1 -1
  113. package/dist/components/ui/select.js +13 -5
  114. package/dist/components/ui/table/table-skeleton.d.ts +2 -2
  115. package/dist/components/ui/table/table.d.ts +1 -1
  116. package/dist/components/ui/table/table.d.ts.map +1 -1
  117. package/dist/components/ui/table/table.js +2 -1
  118. package/dist/components/ui/table/types.d.ts +1 -0
  119. package/dist/components/ui/table/types.d.ts.map +1 -1
  120. package/dist/{devices-7FDK2CGV.cjs → devices-TO4VUO7F.cjs} +9 -9
  121. package/dist/{embeddable-chat-3FR2RCHM.cjs → embeddable-chat-CEAP424I.cjs} +73 -73
  122. package/dist/index.cjs +15 -14
  123. package/dist/{knowledge-ZLHYBG4L.cjs → knowledge-CYJP4XYY.cjs} +9 -9
  124. package/dist/{logs-G6AFN7A6.cjs → logs-NL3ZBQKC.cjs} +16 -16
  125. package/dist/{policies-SEOK4IQO.cjs → policies-WTUV4EIT.cjs} +12 -12
  126. package/dist/{remote-session-WVORQV7V.cjs → remote-session-TC3BZDBZ.cjs} +4 -4
  127. package/dist/{scripts-HH4YVUBO.cjs → scripts-EE6MLBQP.cjs} +15 -15
  128. package/dist/{software-update-4A3XJZQB.cjs → software-update-HX6PN4SB.cjs} +12 -12
  129. package/dist/types/category.d.ts +5 -0
  130. package/dist/types/category.d.ts.map +1 -1
  131. package/dist/types/logs.types.d.ts +2 -0
  132. package/dist/types/logs.types.d.ts.map +1 -1
  133. package/package.json +1 -1
  134. package/src/components/case-studies/quote-cards-strip.tsx +16 -7
  135. package/src/components/categories-cart.tsx +16 -5
  136. package/src/components/chat/entity-cards/admin-content-card-grid.tsx +5 -1
  137. package/src/components/chat/entity-cards/program-card.tsx +6 -3
  138. package/src/components/faq/faq-section.tsx +7 -4
  139. package/src/components/faq-accordion.tsx +2 -4
  140. package/src/components/features/capability-explorer.tsx +16 -4
  141. package/src/components/features/cards-strip.tsx +3 -3
  142. package/src/components/features/model-token-rates.tsx +4 -4
  143. package/src/components/features/remote-session/remote-desktop-chat-panel.tsx +4 -4
  144. package/src/components/help-center-pages/trust-center-page.tsx +1 -1
  145. package/src/components/help-center-pages/trust-center-sections.tsx +15 -22
  146. package/src/components/logs-list.tsx +6 -5
  147. package/src/components/navigation/multi-level-navigation.tsx +6 -7
  148. package/src/components/onboarding-guides/onboarding-guides-catalog-skeleton.tsx +6 -6
  149. package/src/components/related-content/related-content-section.tsx +6 -27
  150. package/src/components/shared/dev-section/dev-card-row.tsx +3 -3
  151. package/src/components/shared/product-release/release-detail-page.tsx +2 -2
  152. package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +8 -2
  153. package/src/components/shared/roadmap/roadmap-grid.tsx +14 -4
  154. package/src/components/tickets/help-center-list.tsx +4 -4
  155. package/src/components/tickets/hooks/use-tickets-list.ts +3 -2
  156. package/src/components/tickets/ticket-center.tsx +4 -13
  157. package/src/components/ui/autocomplete.tsx +12 -3
  158. package/src/components/ui/data-table/data-table-body.tsx +8 -2
  159. package/src/components/ui/data-table/data-table-infinite-footer.tsx +10 -3
  160. package/src/components/ui/data-table/utils.ts +18 -0
  161. package/src/components/ui/search-input.tsx +9 -3
  162. package/src/components/ui/searchable-select.tsx +18 -9
  163. package/src/components/ui/select.tsx +28 -7
  164. package/src/components/ui/table/table-skeleton.tsx +2 -2
  165. package/src/components/ui/table/table.tsx +4 -1
  166. package/src/components/ui/table/types.ts +2 -1
  167. package/src/types/category.ts +5 -0
  168. package/src/types/logs.types.ts +2 -0
@@ -90,7 +90,7 @@ export interface FaqSectionProps {
90
90
  ask?: false | FaqAskOptions;
91
91
  /**
92
92
  * The loading state's groups of questions, in order: THE one way to say what the skeleton draws (see
93
- * `FaqSkeletonShape`). Absent: one group with no category heading, of `minResults` rows (else one screen of rows).
93
+ * `FaqSkeletonShape`). Absent: one group with no category heading, of `minResults` rows (else a typical count).
94
94
  */
95
95
  skeletonShape?: FaqSkeletonShape;
96
96
  /**
@@ -133,14 +133,17 @@ const CATEGORY_SECTION_CLASS = 'scroll-mt-24';
133
133
  * the host declared its FAQs categorised (`skeletonNav`): then every group it draws is named, loading and loaded.
134
134
  */
135
135
  const CATEGORY_HEADING_CLASS = cn(SECTION_HEADING_CLASS, 'pb-4');
136
- /** Questions the block's skeleton draws when the host states no count (`minResults`): one screen of the accordion's rows. */
136
+ /**
137
+ * Questions the block's skeleton draws when the host states no count (`minResults`): the accordion's typical count. A
138
+ * host that fetches with `minResults` gets exactly that many rows.
139
+ */
137
140
  export const FAQ_SECTION_SKELETON_QUESTIONS = FAQ_ACCORDION_SKELETON_ROWS;
138
141
 
139
142
  /** One group of questions the FAQ's loading state draws. */
140
143
  export interface FaqSkeletonGroup {
141
144
  /** The group is a named category: drawn with its heading bar (and counted for the category nav). Default false. */
142
145
  heading?: boolean;
143
- /** Question rows the group draws. Default `FAQ_SECTION_SKELETON_QUESTIONS`, one screen of rows. */
146
+ /** Question rows the group draws: the group's count. Default `FAQ_SECTION_SKELETON_QUESTIONS`, a typical count. */
144
147
  rows?: number;
145
148
  }
146
149
 
@@ -657,7 +660,7 @@ export interface FaqSectionSkeletonProps extends Pick<
657
660
  FaqSectionProps,
658
661
  'heading' | 'topic' | 'showTopic' | 'aside' | 'className' | 'id'
659
662
  > {
660
- /** The groups drawn, in order. Default one group with no heading, of one screen of rows. */
663
+ /** The groups drawn, in order. Default one group with no heading, of `FAQ_SECTION_SKELETON_QUESTIONS` rows. */
661
664
  shape?: FaqSkeletonShape;
662
665
  /** Draw the category nav's row (one line of pills) whatever `sections` is: the host's FAQs are categorised. */
663
666
  navRow?: boolean;
@@ -25,10 +25,8 @@ const ROW_HEADER_CLASS = 'flex w-full items-center gap-6 px-6 py-4 text-left con
25
25
  const QUESTION_CLASS = 'min-w-0 flex-1 break-words';
26
26
  const CHEVRON_CLASS = 'shrink-0 text-ods-text-primary';
27
27
  /**
28
- * Rows the accordion's skeleton draws when not told how many: one screen of closed rows, from the row's layout. A
29
- * closed row is `py-4` around one `text-h3` line from `content-md` (57px with its divider: 13 under a 900px desktop
30
- * viewport once the block's heading sits above them) and about one and a half lines on a phone (about 62px: 12 under
31
- * an 844px one). 12 fills the first screen at both widths without drawing a second.
28
+ * Rows the accordion's skeleton draws when not told how many: a typical count for a list of questions whose host
29
+ * states no count (a host that fetches with a count passes it, and the skeleton draws exactly that, at every width).
32
30
  */
33
31
  export const FAQ_ACCORDION_SKELETON_ROWS = 12;
34
32
  /**
@@ -85,7 +85,12 @@ export interface CapabilityExplorerProps {
85
85
  responderMark?: ReactNode;
86
86
  /** The jobs are being read (the host's query runs): the explorer draws its own skeleton. A page's loading scope does too. */
87
87
  loading?: boolean;
88
- /** The jobs the skeleton's list draws: one screen of the host's list. */
88
+ /**
89
+ * The jobs the skeleton's list draws: the number of jobs the host reads (its limit). Default
90
+ * `CAPABILITY_EXPLORER_SKELETON_JOBS`, a typical count for a host that states none.
91
+ */
92
+ skeletonCount?: number;
93
+ /** @deprecated Use `skeletonCount`. */
89
94
  fold?: number;
90
95
  className?: string;
91
96
  }
@@ -241,6 +246,9 @@ function JobHeadSkeleton() {
241
246
  );
242
247
  }
243
248
 
249
+ /** The jobs the explorer's skeleton lists when its host states no count: a typical count. */
250
+ const CAPABILITY_EXPLORER_SKELETON_JOBS = 8;
251
+
244
252
  /**
245
253
  * The explorer while its data loads, drawn as both layouts in their own boxes
246
254
  * (the width decides which shows, by CSS, since it is not known yet): the list
@@ -251,11 +259,12 @@ function JobHeadSkeleton() {
251
259
  export function CapabilityExplorerSkeleton({
252
260
  region = true,
253
261
  className,
254
- count = 8,
262
+ count = CAPABILITY_EXPLORER_SKELETON_JOBS,
255
263
  }: {
256
264
  /** The loading state announces itself (`LoadingRegion`). `false` inside another loading region. Default `true`. */
257
265
  region?: boolean;
258
266
  className?: string;
267
+ /** The jobs the list draws: the jobs the host reads. Default a typical count. */
259
268
  count?: number;
260
269
  }) {
261
270
  return (
@@ -355,6 +364,7 @@ export function CapabilityExplorer({
355
364
  requesterMark,
356
365
  responderMark,
357
366
  loading = false,
367
+ skeletonCount,
358
368
  fold,
359
369
  className,
360
370
  }: CapabilityExplorerProps) {
@@ -364,10 +374,12 @@ export function CapabilityExplorer({
364
374
 
365
375
  // While the page is read its jobs are not known (none in a loading state): the explorer is its skeleton, never
366
376
  // left out. The page's loading region announces it, so this one does not; a host's own read does.
367
- if (inScope || loading) return <CapabilityExplorerSkeleton region={!inScope} count={fold} className={className} />;
377
+ if (inScope || loading) {
378
+ return <CapabilityExplorerSkeleton region={!inScope} count={skeletonCount ?? fold} className={className} />;
379
+ }
368
380
  if (items.length === 0) return null;
369
381
  // Unknown until measured: one box, so neither layout's ids are in the page twice.
370
- if (wide === undefined) return <CapabilityExplorerSkeleton className={className} />;
382
+ if (wide === undefined) return <CapabilityExplorerSkeleton count={items.length} className={className} />;
371
383
 
372
384
  if (!wide) {
373
385
  return (
@@ -120,8 +120,8 @@ interface CardsStripEngineProps {
120
120
  className?: string;
121
121
  /**
122
122
  * The strip in a page's loading state, when its items are read (none handed in yet): `count` copies of `card`, the
123
- * strip's cards' own skeleton (in children mode each in the managed cell's width). `count` is the strip's fold:
124
- * the cards one screen of the strip shows. Absent: an empty strip draws nothing, in a scope too.
123
+ * strip's cards' own skeleton (in children mode each in the managed cell's width). `count` is the number of cards
124
+ * the host reads (its limit), drawn at every width. Absent: an empty strip draws nothing, in a scope too.
125
125
  */
126
126
  skeleton?: { card: React.ReactNode; count: number };
127
127
  }
@@ -807,7 +807,7 @@ export function CardsStrip<T = unknown>(props: CardsStripProps<T>): React.ReactE
807
807
  // Outside a loading state an empty strip is nothing; inside one, a strip whose host states a skeleton draws it
808
808
  // (a host that states none expects no strip, e.g. a record with no bites).
809
809
  if (!inScope || !skeleton) return null;
810
- // The items are read: the heading (the page's own words), the slot, and a fold of the cards' own skeleton.
810
+ // The items are read: the heading (the page's own words), the slot, and `count` of the cards' own skeleton.
811
811
  return (
812
812
  <div className={cn(STRIP_ROOT_CLASS, className)}>
813
813
  {heading}
@@ -184,7 +184,7 @@ export interface ModelTokenRatesProps {
184
184
  className?: string;
185
185
  }
186
186
 
187
- /** How many rows the table's skeleton draws: about what the panel's height shows. */
187
+ /** Rows the table's skeleton draws: a typical count (a provider's tab lists every rate it has, with no page or limit). */
188
188
  const TABLE_SKELETON_ROWS = 8;
189
189
 
190
190
  /**
@@ -414,8 +414,8 @@ export interface ModelTokenExchangeProps {
414
414
  export const MODEL_TOKEN_EXCHANGE_BALANCE = 1_000_000;
415
415
  export const MODEL_TOKEN_EXCHANGE_RANGE = { min: 1_000_000, max: 50_000_000, step: 1_000_000 } as const;
416
416
 
417
- /** The provider rows drawn while the providers load: the step's fold (three rows beside the calculator's step). */
418
- const PROVIDER_ROWS_FOLD = 3;
417
+ /** The provider rows drawn while the providers load: a typical count (the host lists every provider with a rate). */
418
+ const PROVIDER_SKELETON_ROWS = 3;
419
419
 
420
420
  /** A model as a picker option: its name, its provider under it, the provider's mark beside it. */
421
421
  function modelOption(rate: ModelTokenRate): AutocompleteOption<string> {
@@ -575,7 +575,7 @@ export function ModelTokenExchange({
575
575
  {/* While the providers load the selector draws its own skeleton (its rows, the option row's own boxes). */}
576
576
  <PushButtonSelector<string>
577
577
  isLoading={providers.length === 0 && !failed}
578
- skeletonCount={PROVIDER_ROWS_FOLD}
578
+ skeletonCount={PROVIDER_SKELETON_ROWS}
579
579
  optionDescriptions={false}
580
580
  options={providers.map(providerType => {
581
581
  // What the server stated for the provider rides its models' rows.
@@ -8,10 +8,10 @@ import { RemoteDesktopChatMessageRow, RemoteDesktopChatMessageRowSkeleton } from
8
8
  import type { RemoteSessionChatMessage } from './types';
9
9
 
10
10
  /**
11
- * Message rows the panel's loading state draws: the panel spans the view's height (about 750px on a 900px screen,
12
- * less the input), and a one-line message is about 60px with its gap.
11
+ * Message rows the panel's loading state draws: a typical count. The panel draws the whole history its host hands it
12
+ * (no page, no limit), so there is no page size to draw.
13
13
  */
14
- const CHAT_PANEL_FOLD = 10;
14
+ const CHAT_PANEL_SKELETON_ROWS = 10;
15
15
 
16
16
  /** Who is typing on the technician's side of the chat: shown on the technician's rows. */
17
17
  export interface RemoteDesktopChatTechnician {
@@ -78,7 +78,7 @@ export function RemoteDesktopChatPanel({
78
78
  // The chat while it is read: a screen of the panel's own message rows, stacked by a one-column grid
79
79
  // (the thread above is already the column flexbox: WEBVIEW-001).
80
80
  <LoadingRegion label="Loading the chat" className="grid grid-cols-1 gap-[var(--spacing-system-xsf)]">
81
- {Array.from({ length: CHAT_PANEL_FOLD }, (_, i) => (
81
+ {Array.from({ length: CHAT_PANEL_SKELETON_ROWS }, (_, i) => (
82
82
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
83
83
  <RemoteDesktopChatMessageRowSkeleton key={i} />
84
84
  ))}
@@ -112,7 +112,7 @@ export interface TrustCenterPageProps {
112
112
  faqSkeletonShape?: FaqSkeletonShape;
113
113
  /**
114
114
  * Rows each panel's loading state draws, for a host whose lists are curated (it knows their lengths: the frameworks
115
- * it is audited against). Absent: the layout's fold, the rows one screen holds.
115
+ * it is audited against). Absent: a typical count, at every width.
116
116
  */
117
117
  panelSkeletonRows?: Partial<Record<TrustCenterPanelId, TrustPanelRows>>;
118
118
  }
@@ -143,7 +143,7 @@ export function AiSection({
143
143
  skeletonRows,
144
144
  }: {
145
145
  practices: TrustCenterAiPractice[];
146
- /** Rows the loading state draws (`TrustPanelRows`). Absent: the layout's fold. */
146
+ /** Rows the loading state draws (`TrustPanelRows`). Absent: a typical count. */
147
147
  skeletonRows?: TrustPanelRows;
148
148
  }) {
149
149
  if (useLoadingScope()) return <TrustPanelSkeleton panel="ai" rows={skeletonRows} />;
@@ -415,7 +415,7 @@ export function DocumentsSection({
415
415
  skeletonRows,
416
416
  }: {
417
417
  documents: TrustCenterDocument[];
418
- /** Rows the loading state draws (`TrustPanelRows`). Absent: the layout's fold. */
418
+ /** Rows the loading state draws (`TrustPanelRows`). Absent: a typical count. */
419
419
  skeletonRows?: TrustPanelRows;
420
420
  /** Where a public document opens (`trustCenterDocumentUrl`); `null` → offer a request instead. */
421
421
  documentUrl: (document: TrustCenterDocument) => string | null;
@@ -554,7 +554,7 @@ export function SubprocessorsSection({
554
554
  skeletonRows,
555
555
  }: {
556
556
  subprocessors: TrustCenterSubprocessor[];
557
- /** Rows the loading state draws (`TrustPanelRows`). Absent: the layout's fold. */
557
+ /** Rows the loading state draws (`TrustPanelRows`). Absent: a typical count. */
558
558
  skeletonRows?: TrustPanelRows;
559
559
  }) {
560
560
  if (useLoadingScope()) return <TrustPanelSkeleton panel="subprocessors" rows={skeletonRows} />;
@@ -634,17 +634,19 @@ export function ContactSection({ onContact }: { onContact: () => void }) {
634
634
  // ---------------------------------------------------------------------------
635
635
 
636
636
  /**
637
- * Rows a panel's skeleton draws, on a phone (below `content-md`) and from `content-md`: the rows of a panel one screen
638
- * holds below its section's heading (a row of a typical description is about 100px on a phone and 80px wide). The
639
- * panels list what the host's data lists, so the skeleton draws the layout's fold, never one record set's length; a
640
- * host whose list is curated passes its length (each section's `skeletonRows`, the page's `panelSkeletonRows`).
637
+ * Rows a panel's skeleton draws when its host states no count. A panel lists every record the host's data holds (no
638
+ * page, no limit), so this is a typical count, drawn at every width; a host whose list is curated passes its length
639
+ * (each section's `skeletonRows`, the page's `panelSkeletonRows`).
641
640
  */
642
- const TRUST_PANEL_FOLD = { phone: 4, wide: 5 } as const;
641
+ const TRUST_PANEL_SKELETON_ROWS = 5;
643
642
 
644
643
  /** The panels whose rows `TrustPanelSkeleton` draws (Controls, Contact and the FAQ draw their own). */
645
644
  export type TrustCenterPanelId = 'compliance' | 'documents' | 'subprocessors' | 'ai';
646
645
 
647
- /** Rows a panel's skeleton draws: one count, or one per breakpoint. */
646
+ /**
647
+ * Rows a panel's skeleton draws: one count, at every width. The `{ phone, wide }` form is kept for hosts that pass
648
+ * it and draws the larger of the two everywhere (a skeleton never hides rows per breakpoint).
649
+ */
648
650
  export type TrustPanelRows = number | { phone: number; wide: number };
649
651
 
650
652
  /**
@@ -663,9 +665,9 @@ const TRUST_PANEL_SHAPES: Record<
663
665
  ai: { mark: 'glyph', action: 'none', lines: { phone: 6, wide: 2 } },
664
666
  };
665
667
 
666
- function panelFold(rows: TrustPanelRows | undefined): { phone: number; wide: number } {
667
- if (rows === undefined) return TRUST_PANEL_FOLD;
668
- return typeof rows === 'number' ? { phone: rows, wide: rows } : rows;
668
+ function panelRowCount(rows: TrustPanelRows | undefined): number {
669
+ if (rows === undefined) return TRUST_PANEL_SKELETON_ROWS;
670
+ return typeof rows === 'number' ? rows : Math.max(rows.phone, rows.wide);
669
671
  }
670
672
 
671
673
  /**
@@ -675,17 +677,8 @@ function panelFold(rows: TrustPanelRows | undefined): { phone: number; wide: num
675
677
  */
676
678
  function TrustPanelSkeleton({ panel, rows }: { panel: TrustCenterPanelId; rows?: TrustPanelRows }) {
677
679
  const shape = TRUST_PANEL_SHAPES[panel];
678
- const fold = panelFold(rows);
679
- const panelRows: PanelRow[] = Array.from({ length: Math.max(fold.phone, fold.wide) }, (_, index) => ({
680
+ const panelRows: PanelRow[] = Array.from({ length: panelRowCount(rows) }, (_, index) => ({
680
681
  id: `skeleton-${index}`,
681
- // A row past a breakpoint's fold is not drawn there; the last row drawn on a phone drops its divider like the
682
- // panel's last row (`last:` cannot see a hidden sibling).
683
- className: cn(
684
- index >= fold.phone && 'hidden content-md:flex',
685
- index >= fold.wide && 'content-md:hidden',
686
- fold.phone < fold.wide && index === fold.phone - 1 && 'content-max-md:border-b-0',
687
- fold.wide < fold.phone && index === fold.wide - 1 && 'content-md:border-b-0',
688
- ),
689
682
  columns: [
690
683
  {
691
684
  // The loaded rows' own cell (leading mark, `text-h4` value over a wrapping `text-h6` label): the panel lays
@@ -18,10 +18,10 @@ const LOG_META_ROW_CLASS = 'flex items-center gap-2';
18
18
  const LOG_SCROLL_CLASS = 'flex-1 overflow-y-auto px-4 py-3';
19
19
  const LOG_FRAME_CLASS = 'relative flex flex-col overflow-hidden rounded-lg border border-ods-border bg-ods-card';
20
20
  /**
21
- * Rows the list's skeleton draws: one screen of the list's default 400px box (a row is a `text-h6` title over a
22
- * `text-h5` time, about 60px with its padding).
21
+ * Rows the list's skeleton draws when its host states no `skeletonRows`: a typical count. A host that reads its logs
22
+ * with a page size or a limit passes it, and the skeleton draws exactly that many rows.
23
23
  */
24
- const LOGS_LIST_FOLD = 6;
24
+ const LOGS_LIST_SKELETON_ROWS = 6;
25
25
 
26
26
  /** A log row while it is read: the severity dot's box, the title and the time as bars. */
27
27
  function LogCardSkeleton() {
@@ -91,6 +91,7 @@ export const LogsList = forwardRef<HTMLDivElement, LogsListProps>(
91
91
  showConnector = true,
92
92
  onLogClick,
93
93
  loading = false,
94
+ skeletonRows = LOGS_LIST_SKELETON_ROWS,
94
95
  emptyMessage = 'No logs to display',
95
96
  className,
96
97
  },
@@ -111,11 +112,11 @@ export const LogsList = forwardRef<HTMLDivElement, LogsListProps>(
111
112
  };
112
113
 
113
114
  if (loading) {
114
- // The list's own frame and scroller with a screen of row skeletons.
115
+ // The list's own frame and scroller with a full page of row skeletons.
115
116
  return (
116
117
  <div ref={ref} className={cn(LOG_FRAME_CLASS, getContainerClasses(), className)} style={getContainerStyles()}>
117
118
  <LoadingRegion label="Loading logs" className={LOG_SCROLL_CLASS}>
118
- {Array.from({ length: LOGS_LIST_FOLD }, (_, i) => (
119
+ {Array.from({ length: skeletonRows }, (_, i) => (
119
120
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
120
121
  <LogCardSkeleton key={i} />
121
122
  ))}
@@ -60,13 +60,12 @@ const NAV_ROW_HEIGHT = {
60
60
  mobile: { row: 'h-11', toggle: 'h-11 w-11', toggleRoom: 'pr-11' },
61
61
  } as const;
62
62
  /**
63
- * How many rows each skeleton draws (the desktop tree, the mobile dropdown): a layout fold, the rows one screen holds.
64
- * A row is 48px (44px in the dropdown) with its border and the list's gap, about 54px (50px): a first screen of 900px
65
- * (844px on a phone) holds 17. The tree opens on the reader's repository, so the skeleton draws that shape: one root
66
- * entry open, the rest of the fold its entries (indented as the tree indents them). It is drawn open FIRST: the root
67
- * order is the source's path sort, and which repositories sort before the reader's needs the read, so a page in a
68
- * later repository differs by one collapsed root row above the open one. A repository holding fewer entries ends
69
- * past the first screen anyway, and the tree's other root entries are list growth below it.
63
+ * How many rows each skeleton draws (the desktop tree, the mobile dropdown) when its host states no count. The tree
64
+ * lists every entry its source holds (no page, no limit), so this is a typical count, drawn at every width. The tree
65
+ * opens on the reader's repository, so the skeleton draws that shape: one root entry open, the rest its entries
66
+ * (indented as the tree indents them). It is drawn open FIRST: the root order is the source's path sort, and which
67
+ * repositories sort before the reader's needs the read, so a page in a later repository differs by one collapsed
68
+ * root row above the open one. A host that knows its entry count passes it as `rows`.
70
69
  */
71
70
  export const MULTI_LEVEL_NAVIGATION_SKELETON_ROWS = 17;
72
71
  export const MOBILE_NAVIGATION_SKELETON_ROWS = 17;
@@ -14,13 +14,13 @@ export const CATALOG_SECTION_COUNT_CLASS =
14
14
  export const CATALOG_LIST_CLASS = 'flex flex-col gap-4';
15
15
 
16
16
  /**
17
- * Catalog cards one screen shows under the search and the section pills (a 1440x900 desktop and a 390x844 phone
18
- * both fit three of the `catalog` card's height before the fold). The section count is the data's: the skeleton
19
- * draws ONE section of this fold, and every loaded section is a `data-loading-item`.
17
+ * Catalog cards the skeleton's section draws: a typical count, at every width. The catalog reads every guide (no
18
+ * page, no limit), so there is no page size to draw. The section count is the data's too: the skeleton draws ONE
19
+ * section, and every loaded section is a `data-loading-item`.
20
20
  */
21
- export const ONBOARDING_CATALOG_CARD_FOLD = 3;
21
+ export const ONBOARDING_CATALOG_SKELETON_CARDS = 3;
22
22
  /**
23
- * The catalog's sections while the guides are read: ONE section of `ONBOARDING_CATALOG_CARD_FOLD` cards in the view's
23
+ * The catalog's sections while the guides are read: ONE section of `ONBOARDING_CATALOG_SKELETON_CARDS` cards in the view's
24
24
  * own classes (its heading and count bars). The view draws it itself (`useLoadingScope() || loading`): its search bar
25
25
  * is real and its section pills draw their own skeleton.
26
26
  */
@@ -35,7 +35,7 @@ export function OnboardingCatalogSectionsSkeleton() {
35
35
  </span>
36
36
  </h2>
37
37
  <ul className={CATALOG_LIST_CLASS}>
38
- {Array.from({ length: ONBOARDING_CATALOG_CARD_FOLD }, (_, i) => (
38
+ {Array.from({ length: ONBOARDING_CATALOG_SKELETON_CARDS }, (_, i) => (
39
39
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cards never reorder
40
40
  <li key={i}>
41
41
  <OnboardingGuideCardSkeleton size="catalog" />
@@ -318,33 +318,13 @@ const GROUP_LIST_CLASS = 'space-y-4';
318
318
  const GROUP_HEADING_CLASS = 'font-semibold text-ods-text-secondary text-h5';
319
319
  // A column with a gap, not space-y: the region's screen-reader status (absolutely positioned) must not take a margin.
320
320
  // The rail is a page section (a `<section>`, loaded and loading). Its groups and each group's cards are read lists
321
- // (`data-loading-item`): how many there are is the data's, and the skeleton draws one fold of each group.
321
+ // (`data-loading-item`): how many there are is the data's, and the skeleton draws ONE FULL PAGE of each group.
322
322
  const RAIL_CLASS = 'flex flex-col gap-8';
323
- /** Cards per group a skeleton draws when the host states no count. */
324
- const RAIL_SKELETON_CARDS = 3;
325
- /**
326
- * One screen of a group, the most its skeleton draws (the host's count is the upper bound): list groups stack
327
- * full-width cards, two of which fill a screen; grid groups show two rows at their widest column count, and the
328
- * cards past two rows at a narrower step are hidden by the same breakpoints the grid uses (`gridClassFor`).
329
- */
330
- const RAIL_LIST_FOLD = 2;
331
- const RAIL_GRID_FOLD_ROWS = 2;
332
323
  /**
333
324
  * The card an unknown-type group draws while its type is read: the rail's grid content card (a cover over a title
334
325
  * and an excerpt), the shape the read types' grid cards share, so the group holds a grid card's height.
335
326
  */
336
327
  const UNKNOWN_TYPE_CARD = 'blog_post_existing';
337
- /** The grid's column count per step, as `gridClassFor` sets it: [phone, sm, lg]. */
338
- const GRID_COLUMNS_BY_STEP: Record<2 | 3, readonly [number, number, number]> = { 2: [1, 2, 2], 3: [1, 2, 3] };
339
-
340
- /** A grid skeleton card's visibility: shown only at the steps where it is within the fold's rows. */
341
- function gridFoldClass(index: number, columns: 2 | 3): string | undefined {
342
- const [phone, sm, lg] = GRID_COLUMNS_BY_STEP[columns].map(c => c * RAIL_GRID_FOLD_ROWS);
343
- if (index < phone) return undefined;
344
- if (index < sm) return 'hidden sm:block';
345
- if (index < lg) return 'hidden lg:block';
346
- return 'hidden';
347
- }
348
328
 
349
329
  /** A group's heading: the type's label (static copy), loaded and loading. */
350
330
  function GroupHeading({ type }: { type: string }) {
@@ -848,9 +828,9 @@ export function RelatedContentSectionSkeleton({
848
828
  <TextLineSkeleton type="inherit" as="span" className="w-24" />
849
829
  </h3>
850
830
  <div className={gridClassFor(columns)}>
851
- {Array.from({ length: Math.max(...GRID_COLUMNS_BY_STEP[columns]) * RAIL_GRID_FOLD_ROWS }, (_, i) => (
831
+ {Array.from({ length: GROUP_PAGE_SIZE }, (_, i) => (
852
832
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cards never reorder
853
- <div key={i} className={gridFoldClass(i, columns)} data-loading-item>
833
+ <div key={i} data-loading-item>
854
834
  {renderSkeletonForType(UNKNOWN_TYPE_CARD, 'default', adminCampaignCard, extras)}
855
835
  </div>
856
836
  ))}
@@ -868,15 +848,14 @@ export function RelatedContentSectionSkeleton({
868
848
  const config = resolveGroupConfig(type);
869
849
  const sameType = canonicalEntityType !== null && canonicalContentRefType(type) === canonicalEntityType;
870
850
  const isList = config.layout === 'list';
871
- const fold = isList ? RAIL_LIST_FOLD : Math.max(...GRID_COLUMNS_BY_STEP[columns]) * RAIL_GRID_FOLD_ROWS;
851
+ // One full page of the group: the count the host fetches, never more than a group's page.
872
852
  const count = Math.min(
873
853
  GROUP_PAGE_SIZE,
874
- fold,
875
- (sameType ? (sameTypeMinResults ?? minResults) : minResults) ?? RAIL_SKELETON_CARDS,
854
+ (sameType ? (sameTypeMinResults ?? minResults) : minResults) ?? GROUP_PAGE_SIZE,
876
855
  );
877
856
  const cards = Array.from({ length: count }, (_, i) => (
878
857
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton cards never reorder
879
- <div key={i} className={isList ? undefined : gridFoldClass(i, columns)} data-loading-item>
858
+ <div key={i} data-loading-item>
880
859
  {renderSkeletonForType(type, config.gridSize, adminCampaignCard, extras)}
881
860
  </div>
882
861
  ));
@@ -120,9 +120,8 @@ export function DevCardRowSkeleton() {
120
120
  }
121
121
 
122
122
  /**
123
- * The standard "5 skeleton rows inside a bordered card" loading state
124
- * used by every list shell. Both delivery (`delivery-table.tsx`) and
125
- * tickets (`tickets-list.tsx`) mount this directly.
123
+ * The skeleton rows inside a bordered card: the loading state of every list shell. A list draws ONE FULL PAGE, so
124
+ * a paged list passes its page size (the tickets list passes its own); `5` is the typical count for a list with none.
126
125
  */
127
126
  export function DevCardRowSkeletonList({
128
127
  region = true,
@@ -130,6 +129,7 @@ export function DevCardRowSkeletonList({
130
129
  }: {
131
130
  /** The loading state announces itself (`LoadingRegion`). `false` inside another loading region. Default `true`. */
132
131
  region?: boolean;
132
+ /** Rows to draw: the list's page size. Default `5`. */
133
133
  rows?: number;
134
134
  }) {
135
135
  return (
@@ -159,7 +159,7 @@ const SUMMARY_CLASS = 'text-ods-text-primary text-h4';
159
159
  * release carries more tags than a phone's line holds (three badges), so the row is capped to that line and drawn the
160
160
  * same while it is read: the badges and the "+N" never move the page under them.
161
161
  */
162
- const RELEASE_TAG_ROW_FOLD = 3;
162
+ const RELEASE_TAG_ROW_MAX = 3;
163
163
  /**
164
164
  * A release tag's typical label width while it is read: a release is tagged with product areas, longer than most tags
165
165
  * (sampled badges measure 64 to 155px, typically about 105px: ten characters of the badge's type), so its row wraps
@@ -400,7 +400,7 @@ export function ReleaseDetailPage({
400
400
  {/* Tags — flat product_release_tags[] from entity_tags */}
401
401
  <EntityTagBadges
402
402
  tags={view.product_release_tags as TagAssoc[] | undefined}
403
- max={RELEASE_TAG_ROW_FOLD}
403
+ max={RELEASE_TAG_ROW_MAX}
404
404
  skeletonItemClassName={RELEASE_TAG_SKELETON_WIDTH_CLASS}
405
405
  />
406
406
 
@@ -17,10 +17,16 @@ export function roadmapGridClass(columns: 1 | 2, showLeftMargin: boolean): strin
17
17
  }`;
18
18
  }
19
19
 
20
+ /**
21
+ * Cards the grid's skeleton draws when its host states no count: a typical count. The roadmap lists every item it
22
+ * holds (no page, no limit); a host that reads a known number of items passes it as `count`.
23
+ */
24
+ export const ROADMAP_GRID_SKELETON_CARDS = 4;
25
+
20
26
  export interface RoadmapGridSkeletonProps {
21
27
  /** The loading state announces itself (`LoadingRegion`). `false` inside another loading region. Default `true`. */
22
28
  region?: boolean;
23
- /** Number of skeleton cards to show. Default 4. */
29
+ /** Skeleton cards: the number of items the host reads. Default `ROADMAP_GRID_SKELETON_CARDS`, a typical count. */
24
30
  count?: number;
25
31
  /** Show the desktop left margin (~120px) that aligns the grid with
26
32
  * the page hero's title block. Default `true`. Related-content rails
@@ -32,7 +38,7 @@ export interface RoadmapGridSkeletonProps {
32
38
 
33
39
  export function RoadmapGridSkeleton({
34
40
  region = true,
35
- count = 4,
41
+ count = ROADMAP_GRID_SKELETON_CARDS,
36
42
  showLeftMargin = true,
37
43
  columns = 2,
38
44
  }: RoadmapGridSkeletonProps) {
@@ -126,7 +126,12 @@ export interface RoadmapGridProps {
126
126
  quartersToKeepClosed?: number;
127
127
  /** The items are being read (the host's query runs): the grid draws its own skeleton. A page's loading scope does too. */
128
128
  isLoading?: boolean;
129
- /** The cards the flat grid's skeleton draws: one screen of the host's grid (default the skeleton's own). */
129
+ /**
130
+ * The cards the grid's skeleton draws: the number of items the host reads (its limit), drawn at every width.
131
+ * Default `ROADMAP_GRID_SKELETON_CARDS`, a typical count for a roadmap that lists everything it holds.
132
+ */
133
+ skeletonCount?: number;
134
+ /** @deprecated Use `skeletonCount`. */
130
135
  fold?: number;
131
136
  }
132
137
 
@@ -184,9 +189,12 @@ const QUARTER_CONTENT_INNER_CLASS = 'pb-4';
184
189
  export function RoadmapGridQuarterSkeleton({
185
190
  showLeftMargin = true,
186
191
  columns = 2,
192
+ count,
187
193
  }: {
188
194
  showLeftMargin?: boolean;
189
195
  columns?: 1 | 2;
196
+ /** The quarter's skeleton cards. Default the grid skeleton's own. */
197
+ count?: number;
190
198
  }) {
191
199
  return (
192
200
  <div className={QUARTER_LIST_CLASS} aria-hidden>
@@ -198,7 +206,7 @@ export function RoadmapGridQuarterSkeleton({
198
206
  {/* The quarter's content box: the `AccordionContent` padding (`pt-4` here, the content's own `pb-4` inside). */}
199
207
  <div className={QUARTER_CONTENT_CLASS}>
200
208
  <div className={QUARTER_CONTENT_INNER_CLASS}>
201
- <RoadmapGridSkeleton region={false} showLeftMargin={showLeftMargin} columns={columns} />
209
+ <RoadmapGridSkeleton region={false} count={count} showLeftMargin={showLeftMargin} columns={columns} />
202
210
  </div>
203
211
  </div>
204
212
  </div>
@@ -217,8 +225,10 @@ export function RoadmapGrid({
217
225
  hasActiveFilters = false,
218
226
  quartersToKeepClosed = 2,
219
227
  isLoading = false,
228
+ skeletonCount,
220
229
  fold,
221
230
  }: RoadmapGridProps) {
231
+ const skeletonCards = skeletonCount ?? fold;
222
232
  // ── Voting (shared across all quarters) ──
223
233
  const { getVote, toggleVote } = useRoadmapVoting(votingOptions);
224
234
  const inScope = useLoadingScope();
@@ -313,9 +323,9 @@ export function RoadmapGrid({
313
323
  // empty state. The page's loading region announces a scope's; a host's own read announces itself.
314
324
  if (inScope || isLoading) {
315
325
  return groupByQuarter ? (
316
- <RoadmapGridQuarterSkeleton showLeftMargin={showLeftMargin} columns={columns} />
326
+ <RoadmapGridQuarterSkeleton showLeftMargin={showLeftMargin} columns={columns} count={skeletonCards} />
317
327
  ) : (
318
- <RoadmapGridSkeleton region={!inScope} count={fold} showLeftMargin={showLeftMargin} columns={columns} />
328
+ <RoadmapGridSkeleton region={!inScope} count={skeletonCards} showLeftMargin={showLeftMargin} columns={columns} />
319
329
  );
320
330
  }
321
331
 
@@ -48,7 +48,7 @@ import { UnifiedPagination } from '../unified-pagination';
48
48
  import { HelpCenterCard } from './help-center-card';
49
49
  import { HelpCenterCreateForm, HelpCenterCreateFormSkeleton } from './help-center-create-form';
50
50
  import { useTicketActions } from './hooks/use-ticket-actions';
51
- import { useTicketsList } from './hooks/use-tickets-list';
51
+ import { TICKETS_PAGE_SIZE, useTicketsList } from './hooks/use-tickets-list';
52
52
  import type { AnyTicket, OptimisticTicket, TicketsCacheSlot } from './types';
53
53
  import { isOptimistic } from './types';
54
54
 
@@ -105,7 +105,7 @@ export function HelpCenterList({ toast = defaultToast, backButton, title, shell
105
105
  shell={shell}
106
106
  preControls={<HelpCenterCreateFormSkeleton />}
107
107
  >
108
- <DevCardRowSkeletonList />
108
+ <DevCardRowSkeletonList rows={TICKETS_PAGE_SIZE} />
109
109
  </DevSectionPage>
110
110
  );
111
111
  }
@@ -206,7 +206,7 @@ function HelpCenterListAuthed({
206
206
  [searchParams, router, pathname],
207
207
  );
208
208
 
209
- const { tickets, isLoading, isFetching, error, refetch, totalPages } = useTicketsList({
209
+ const { tickets, isLoading, isFetching, error, refetch, totalPages, pageSize } = useTicketsList({
210
210
  // `sessionEmail` is drilled in from the parent — see the same
211
211
  // pattern + race-cause rationale documented in
212
212
  // `HelpCenterCreateForm.sessionName/sessionEmail`. Calling
@@ -330,7 +330,7 @@ function HelpCenterListAuthed({
330
330
  {!error && (
331
331
  <div className="w-full">
332
332
  {isLoading ? (
333
- <DevCardRowSkeletonList />
333
+ <DevCardRowSkeletonList rows={pageSize} />
334
334
  ) : !hasResults && isFetching ? (
335
335
  // Bridge state — background refetch in flight and the
336
336
  // optimistic placeholder was just removed by the mutation
@@ -27,7 +27,8 @@ import { PAGE_PARAM_LIMITS, pageCount, positiveInt } from '../../../utils/search
27
27
  import type { TicketData } from '../types';
28
28
 
29
29
  const FIND_TICKET_ENDPOINT = '/api/chat/agent/find-ticket';
30
- const DEFAULT_PAGE_SIZE = 20;
30
+ /** The tickets a page of the list loads unless the host asks for another size; also its skeleton's row count. */
31
+ export const TICKETS_PAGE_SIZE = 20;
31
32
 
32
33
  interface FindTicketResponse {
33
34
  tickets?: TicketData[];
@@ -101,7 +102,7 @@ export function useTicketsList(filters: UseTicketsListFilters): UseTicketsListRe
101
102
  const status = (filters.status ?? '').trim().toLowerCase();
102
103
  const statusFilter = status && status !== 'all' ? status : '';
103
104
  const page = positiveInt(filters.page, 1);
104
- const pageSize = positiveInt(filters.pageSize, DEFAULT_PAGE_SIZE, { max: PAGE_PARAM_LIMITS.maxPageSize });
105
+ const pageSize = positiveInt(filters.pageSize, TICKETS_PAGE_SIZE, { max: PAGE_PARAM_LIMITS.maxPageSize });
105
106
 
106
107
  // `customerEmail` is the source of truth — parent (HelpCenterList)
107
108
  // already gates on `identity.user?.email` being truthy before