@eifi1/ui-kit 0.45.0 → 0.46.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 (228) hide show
  1. package/README.md +23 -12
  2. package/dist/admin/admin-area-labels.d.ts +9 -0
  3. package/dist/admin/admin-area-labels.js +5 -1
  4. package/dist/admin/admin-area-labels.js.map +1 -1
  5. package/dist/admin/admin-overview.d.ts +16 -1
  6. package/dist/admin/admin-overview.js +12 -1
  7. package/dist/admin/admin-overview.js.map +1 -1
  8. package/dist/admin/mail-test-card.d.ts +3 -2
  9. package/dist/admin/mail-test-card.js +3 -1
  10. package/dist/admin/mail-test-card.js.map +1 -1
  11. package/dist/components/appearance-labels.d.ts +10 -0
  12. package/dist/components/appearance-labels.js +6 -1
  13. package/dist/components/appearance-labels.js.map +1 -1
  14. package/dist/components/data-table-column-settings.js +2 -2
  15. package/dist/components/data-table-column-settings.js.map +1 -1
  16. package/dist/components/data-table-head.js +2 -2
  17. package/dist/components/data-table-head.js.map +1 -1
  18. package/dist/components/data-table-row-actions.d.ts +18 -2
  19. package/dist/components/data-table-row-actions.js +86 -2
  20. package/dist/components/data-table-row-actions.js.map +1 -1
  21. package/dist/components/data-table-shared.js +9 -2
  22. package/dist/components/data-table-shared.js.map +1 -1
  23. package/dist/components/data-table.d.ts +43 -2
  24. package/dist/components/data-table.js +9 -4
  25. package/dist/components/data-table.js.map +1 -1
  26. package/dist/components/date-format-setting.d.ts +29 -0
  27. package/dist/components/date-format-setting.js +36 -0
  28. package/dist/components/date-format-setting.js.map +1 -0
  29. package/dist/components/swipeable-row.d.ts +8 -0
  30. package/dist/components/swipeable-row.js.map +1 -1
  31. package/dist/components/tabs.d.ts +3 -2
  32. package/dist/components/tabs.js +1 -1
  33. package/dist/components/tabs.js.map +1 -1
  34. package/dist/components/translation-review-editor.js +1 -0
  35. package/dist/components/translation-review-editor.js.map +1 -1
  36. package/dist/components/translation-review-labels.d.ts +5 -0
  37. package/dist/components/translation-review-labels.js +2 -0
  38. package/dist/components/translation-review-labels.js.map +1 -1
  39. package/dist/components/translation-review.d.ts +18 -8
  40. package/dist/components/translation-review.js +64 -7
  41. package/dist/components/translation-review.js.map +1 -1
  42. package/dist/components/write-lock-labels.js +2 -1
  43. package/dist/components/write-lock-labels.js.map +1 -1
  44. package/dist/components/write-lock.d.ts +33 -3
  45. package/dist/components/write-lock.js +9 -0
  46. package/dist/components/write-lock.js.map +1 -1
  47. package/dist/data-table.d.ts +1 -1
  48. package/dist/data-table.js.map +1 -1
  49. package/dist/eslint/index.js +1 -1
  50. package/dist/feedback/feedback-row-detail.d.ts +4 -2
  51. package/dist/feedback/feedback-row-detail.js +17 -26
  52. package/dist/feedback/feedback-row-detail.js.map +1 -1
  53. package/dist/feedback/feedback-swipe.d.ts +18 -0
  54. package/dist/feedback/feedback-swipe.js +10 -1
  55. package/dist/feedback/feedback-swipe.js.map +1 -1
  56. package/dist/i18n/german.js +41 -5
  57. package/dist/i18n/german.js.map +1 -1
  58. package/dist/i18n/kit-labels.d.ts +12 -1
  59. package/dist/i18n/kit-labels.js +8 -2
  60. package/dist/i18n/kit-labels.js.map +1 -1
  61. package/dist/i18n/locales/en.js +40 -6
  62. package/dist/i18n/locales/en.js.map +2 -2
  63. package/dist/i18n/locales/es.js +41 -5
  64. package/dist/i18n/locales/es.js.map +1 -1
  65. package/dist/i18n/locales/fr.js +41 -5
  66. package/dist/i18n/locales/fr.js.map +1 -1
  67. package/dist/i18n/locales/hu.js +41 -5
  68. package/dist/i18n/locales/hu.js.map +1 -1
  69. package/dist/i18n/locales/it.js +41 -5
  70. package/dist/i18n/locales/it.js.map +1 -1
  71. package/dist/i18n/locales/zh.js +41 -5
  72. package/dist/i18n/locales/zh.js.map +1 -1
  73. package/dist/i18n/review.js +3 -1
  74. package/dist/i18n/review.js.map +1 -1
  75. package/dist/index.d.ts +15 -2
  76. package/dist/index.js +35 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/lib/date-format.d.ts +81 -0
  79. package/dist/lib/date-format.js +56 -0
  80. package/dist/lib/date-format.js.map +1 -0
  81. package/dist/lib/format.d.ts +3 -0
  82. package/dist/lib/format.js +5 -4
  83. package/dist/lib/format.js.map +1 -1
  84. package/dist/notifications/device-zone-report.d.ts +27 -0
  85. package/dist/notifications/device-zone-report.js +24 -0
  86. package/dist/notifications/device-zone-report.js.map +1 -0
  87. package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
  88. package/dist/notifications/feedback-notification-types-labels.js +4 -1
  89. package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
  90. package/dist/notifications/feedback-notification-types.d.ts +11 -5
  91. package/dist/notifications/feedback-notification-types.js +2 -1
  92. package/dist/notifications/feedback-notification-types.js.map +1 -1
  93. package/dist/notifications/notification-bell.d.ts +33 -0
  94. package/dist/notifications/notification-bell.js +74 -0
  95. package/dist/notifications/notification-bell.js.map +1 -0
  96. package/dist/notifications/notification-inbox.d.ts +77 -0
  97. package/dist/notifications/notification-inbox.js +206 -0
  98. package/dist/notifications/notification-inbox.js.map +1 -0
  99. package/dist/notifications/notification-types-setting.d.ts +6 -4
  100. package/dist/notifications/notification-types-setting.js +103 -45
  101. package/dist/notifications/notification-types-setting.js.map +1 -1
  102. package/dist/notifications/notification-types.d.ts +19 -7
  103. package/dist/notifications/notifications-labels.d.ts +41 -7
  104. package/dist/notifications/notifications-labels.js +18 -1
  105. package/dist/notifications/notifications-labels.js.map +1 -1
  106. package/dist/notifications/push-devices-setting.d.ts +20 -3
  107. package/dist/notifications/push-devices-setting.js +93 -33
  108. package/dist/notifications/push-devices-setting.js.map +1 -1
  109. package/dist/notifications/push-worker.d.ts +108 -0
  110. package/dist/notifications/push-worker.js +62 -0
  111. package/dist/notifications/push-worker.js.map +1 -0
  112. package/dist/notifications/quiet-hours-setting.d.ts +29 -17
  113. package/dist/notifications/quiet-hours-setting.js +9 -41
  114. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  115. package/dist/notifications/time-zone-select.d.ts +5 -2
  116. package/dist/notifications/time-zone-select.js.map +1 -1
  117. package/dist/notifications/use-push-device.d.ts +4 -17
  118. package/dist/notifications/use-push-device.js +7 -15
  119. package/dist/notifications/use-push-device.js.map +1 -1
  120. package/dist/offline/local-first.d.ts +33 -0
  121. package/dist/offline/local-first.js +39 -0
  122. package/dist/offline/local-first.js.map +1 -0
  123. package/dist/offline/query-persistence.d.ts +26 -1
  124. package/dist/offline/query-persistence.js +27 -23
  125. package/dist/offline/query-persistence.js.map +1 -1
  126. package/dist/offline/service-worker-updates.d.ts +46 -0
  127. package/dist/offline/service-worker-updates.js +39 -0
  128. package/dist/offline/service-worker-updates.js.map +1 -0
  129. package/dist/offline.d.ts +5 -2
  130. package/dist/offline.js +11 -2
  131. package/dist/offline.js.map +1 -1
  132. package/dist/push-worker.d.ts +2 -0
  133. package/dist/push-worker.js +14 -0
  134. package/dist/push-worker.js.map +1 -0
  135. package/dist/shell/account-menu-text-size.d.ts +33 -0
  136. package/dist/shell/account-menu-text-size.js +51 -0
  137. package/dist/shell/account-menu-text-size.js.map +1 -0
  138. package/dist/shell/top-bar-actions.d.ts +6 -2
  139. package/dist/shell/top-bar-actions.js +1 -0
  140. package/dist/shell/top-bar-actions.js.map +1 -1
  141. package/dist/shell.d.ts +2 -0
  142. package/dist/shell.js +2 -0
  143. package/dist/shell.js.map +1 -1
  144. package/dist/sync/db.d.ts +76 -0
  145. package/dist/sync/db.js +24 -0
  146. package/dist/sync/db.js.map +1 -0
  147. package/dist/sync/engine.d.ts +124 -0
  148. package/dist/sync/engine.js +474 -0
  149. package/dist/sync/engine.js.map +1 -0
  150. package/dist/sync/hooks.d.ts +30 -0
  151. package/dist/sync/hooks.js +67 -0
  152. package/dist/sync/hooks.js.map +1 -0
  153. package/dist/sync/testing.d.ts +28 -0
  154. package/dist/sync/testing.js +41 -0
  155. package/dist/sync/testing.js.map +1 -0
  156. package/dist/sync/wire.d.ts +50 -0
  157. package/dist/sync/wire.js +2 -0
  158. package/dist/sync/wire.js.map +1 -0
  159. package/dist/sync.d.ts +7 -0
  160. package/dist/sync.js +22 -0
  161. package/dist/sync.js.map +1 -0
  162. package/dist/tour/tour-labels.js +1 -0
  163. package/dist/tour/tour-labels.js.map +1 -1
  164. package/dist/tour/tour.d.ts +30 -0
  165. package/dist/tour/tour.js +50 -6
  166. package/dist/tour/tour.js.map +1 -1
  167. package/package.json +19 -1
  168. package/src/admin/admin-area-labels.ts +13 -0
  169. package/src/admin/admin-overview.tsx +37 -2
  170. package/src/admin/mail-test-card.tsx +7 -3
  171. package/src/components/appearance-labels.ts +16 -0
  172. package/src/components/data-table-column-settings.tsx +2 -2
  173. package/src/components/data-table-head.tsx +2 -2
  174. package/src/components/data-table-row-actions.tsx +118 -2
  175. package/src/components/data-table-shared.tsx +21 -2
  176. package/src/components/data-table.tsx +51 -6
  177. package/src/components/date-format-setting.tsx +64 -0
  178. package/src/components/swipeable-row.tsx +8 -0
  179. package/src/components/tabs.tsx +4 -3
  180. package/src/components/translation-review-editor.tsx +2 -0
  181. package/src/components/translation-review-labels.ts +7 -0
  182. package/src/components/translation-review.tsx +102 -14
  183. package/src/components/write-lock-labels.ts +1 -0
  184. package/src/components/write-lock.tsx +36 -3
  185. package/src/data-table.ts +1 -0
  186. package/src/feedback/feedback-row-detail.tsx +18 -23
  187. package/src/feedback/feedback-swipe.tsx +28 -1
  188. package/src/i18n/german.ts +44 -4
  189. package/src/i18n/kit-labels.tsx +18 -1
  190. package/src/i18n/locales/en.ts +1 -0
  191. package/src/i18n/locales/es.ts +44 -4
  192. package/src/i18n/locales/fr.ts +44 -4
  193. package/src/i18n/locales/hu.ts +44 -4
  194. package/src/i18n/locales/it.ts +44 -4
  195. package/src/i18n/locales/zh.ts +44 -2
  196. package/src/i18n/review.ts +3 -1
  197. package/src/index.ts +42 -0
  198. package/src/lib/date-format.ts +151 -0
  199. package/src/lib/format.ts +10 -4
  200. package/src/notifications/device-zone-report.ts +54 -0
  201. package/src/notifications/feedback-notification-types-labels.ts +10 -1
  202. package/src/notifications/feedback-notification-types.ts +12 -5
  203. package/src/notifications/notification-bell.tsx +116 -0
  204. package/src/notifications/notification-inbox.tsx +304 -0
  205. package/src/notifications/notification-types-setting.tsx +151 -55
  206. package/src/notifications/notification-types.ts +20 -8
  207. package/src/notifications/notifications-labels.ts +69 -11
  208. package/src/notifications/push-devices-setting.tsx +123 -34
  209. package/src/notifications/push-worker.ts +160 -0
  210. package/src/notifications/quiet-hours-setting.tsx +41 -58
  211. package/src/notifications/time-zone-select.tsx +5 -2
  212. package/src/notifications/use-push-device.ts +20 -33
  213. package/src/offline/local-first.ts +76 -0
  214. package/src/offline/query-persistence.ts +57 -24
  215. package/src/offline/service-worker-updates.ts +81 -0
  216. package/src/offline.ts +7 -1
  217. package/src/push-worker.ts +25 -0
  218. package/src/shell/account-menu-text-size.tsx +77 -0
  219. package/src/shell/top-bar-actions.tsx +6 -1
  220. package/src/shell.ts +3 -0
  221. package/src/sync/db.ts +103 -0
  222. package/src/sync/engine.ts +699 -0
  223. package/src/sync/hooks.ts +93 -0
  224. package/src/sync/testing.ts +73 -0
  225. package/src/sync/wire.ts +61 -0
  226. package/src/sync.ts +29 -0
  227. package/src/tour/tour-labels.ts +1 -0
  228. package/src/tour/tour.tsx +100 -7
package/README.md CHANGED
@@ -67,6 +67,7 @@ when it chooses. To work on the design system and an app together, use
67
67
  | `modern-screenshot` | peer, `^4.7.0`, **optional** | The feedback dialog's "Capture screenshot" (`captureAppScreenshot`), reached only through `await import()`. Without it the capture rejects and the dialog toasts `feedbackToast.captureFailed`. |
68
68
  | `@tanstack/react-query`, `@tanstack/react-query-persist-client`, `@tanstack/query-async-storage-persister` | peer, `^5`, **optional** | Only `@eifi1/ui-kit/offline` imports them (0.45, the persisted query cache, `createQueryPersistence`); the main barrel does not. |
69
69
  | `idb-keyval` | peer, `^6`, **optional** | The persisted cache's default IndexedDB store, in `@eifi1/ui-kit/offline` only. |
70
+ | `dexie` | peer, `^4`, **optional** | The sync engine's device copy, in `@eifi1/ui-kit/sync` only (0.46); `/offline` loads without it. |
70
71
  | `clsx`, `tailwind-merge`, `zustand` | dependency | Small, imported directly, and the kit owns the version. Add `zustand` to Vite's `dedupe` (below) — the appearance stores are singletons. |
71
72
  | `flag-icons` | **nothing — you supply the stylesheet** | See below. |
72
73
 
@@ -558,20 +559,20 @@ columns, strings and permissions (see the note at the top of `src/feedback/feedb
558
559
 
559
560
  <!-- BEGIN GENERATED: exports — node scripts/gen-export-inventory.mjs -->
560
561
 
561
- **2094 names from 342 modules** — 965 values and 1129 types. _Italic_ is a type-only export.
562
+ **2133 names from 349 modules** — 985 values and 1148 types. _Italic_ is a type-only export.
562
563
 
563
564
  Generated from `dist/index.d.ts` by `node scripts/gen-export-inventory.mjs`; the count
564
565
  is pinned by `src/__tests__/public-surface.test.ts`. Do not edit between the markers.
565
566
 
566
567
  | Entry point | Names |
567
568
  |---|---|
568
- | `@eifi1/ui-kit` | 2094 |
569
+ | `@eifi1/ui-kit` | 2133 |
569
570
  | `@eifi1/ui-kit/chart` | 92 |
570
- | `@eifi1/ui-kit/shell` | 112 |
571
- | `@eifi1/ui-kit/data-table` | 51 |
571
+ | `@eifi1/ui-kit/shell` | 114 |
572
+ | `@eifi1/ui-kit/data-table` | 52 |
572
573
  | `@eifi1/ui-kit/wizard` | 24 |
573
574
  | `@eifi1/ui-kit/tour` | 37 |
574
- | `@eifi1/ui-kit/feedback` | 205 |
575
+ | `@eifi1/ui-kit/feedback` | 206 |
575
576
  | `@eifi1/ui-kit/search` | 23 |
576
577
  | `@eifi1/ui-kit/dates` | 22 |
577
578
  | `@eifi1/ui-kit/table-text` | 14 |
@@ -579,7 +580,10 @@ is pinned by `src/__tests__/public-surface.test.ts`. Do not edit between the mar
579
580
  | `@eifi1/ui-kit/eslint` | 1 |
580
581
  | `@eifi1/ui-kit/testing` | 11 |
581
582
  | `@eifi1/ui-kit/rhf` | 48 |
582
- | `@eifi1/ui-kit/offline` | 22 |
583
+ | `@eifi1/ui-kit/offline` | 32 |
584
+ | `@eifi1/ui-kit/sync` | 35 |
585
+ | `@eifi1/ui-kit/sync/testing` | 3 |
586
+ | `@eifi1/ui-kit/push-worker` | 10 |
583
587
 
584
588
  Everything below is reachable from the main `@eifi1/ui-kit` barrel. The subpaths are a
585
589
  re-slicing of it, never a second API.
@@ -605,6 +609,7 @@ re-slicing of it, never a second API.
605
609
  | `lib/isin` | `formatIsin`, `isinCheckDigit`, `isValidIsin` |
606
610
  | `lib/phone` | `formatNationalPhone`, `formatPhone`, `isE164`, `parsePhone`, `PHONE_COUNTRIES`, `PHONE_DIAL_CODES`, _`ParsedPhone`_, _`PhoneCountry`_, _`PhoneCountryCode`_ |
607
611
  | `lib/column-mapping` | `assignColumnRole`, `guessMapping`, `missingRoles`, `readMappedTable`, `readTextFile`, `roleOfColumn`, _`ColumnMapperResult`_, _`ColumnMapping`_, _`ColumnRole`_ |
612
+ | `lib/date-format` | `DATE_FORMAT_PREFERENCES`, `dateFormatSample`, `dateFormatterFor`, `formatDateWithPreference`, `isDateFormatPreference`, `resolveDateFormat`, _`DateFormatOptions`_, _`DateFormatPreference`_, _`ResolvedDateFormat`_ |
608
613
  | `lib/device-settings` | `deviceSettings`, `registerDeviceSetting`, `resetDeviceSettings`, _`DeviceSetting`_, _`DeviceSettingKind`_ |
609
614
  | `lib/clipping` | `CLIPS_ATTRIBUTE` |
610
615
  | `lib/chip-edge-snap` | `refreshChipEdges` |
@@ -652,7 +657,7 @@ re-slicing of it, never a second API.
652
657
 
653
658
  | Module | Exports |
654
659
  |---|---|
655
- | `i18n/kit-labels` | `DEFAULT_APP_SHELL_LABELS`, `DEFAULT_CALCULATOR_LABELS`, `DEFAULT_COMBOBOX_LABELS`, `DEFAULT_COMMON_LABELS`, `DEFAULT_CURRENCY_LABELS`, `DEFAULT_DATE_PICKER_LABELS`, `DEFAULT_FILE_LABELS`, `DEFAULT_MULTI_SELECT_LABELS`, `DEFAULT_PICKER_SHEET_LABELS`, `DEFAULT_SWIPEABLE_ROW_LABELS`, `DEFAULT_TOP_BAR_LABELS`, `formatFileSize`, `missingKitLabels`, `UiKitProvider`, `useKitChartTooltipPlacement`, `useKitDateFormatter`, `useKitFileLabels`, `useKitLabelOverrides`, `useKitLabels`, `useKitLink`, `useKitLocale`, `useKitTableStorageKeyPrefix`, `useKitWeekStart`, _`AppShellLabels`_, _`CalculatorLabels`_, _`ChartTooltipPlacement`_, _`ComboboxLabels`_, _`CommonLabels`_, _`CurrencyLabels`_, _`DatePickerLabels`_, _`FileLabels`_, _`KitDateFormatContext`_, _`KitDateFormatter`_, _`KitLinkComponent`_, _`KitLinkProps`_, _`LabelOverride`_, _`MultiSelectLabels`_, _`PickerSheetLabels`_, _`SwipeableRowLabels`_, _`TopBarLabels`_, _`UiKitLabelOverrides`_, _`UiKitLabels`_, _`UiKitProviderProps`_ |
660
+ | `i18n/kit-labels` | `DEFAULT_APP_SHELL_LABELS`, `DEFAULT_CALCULATOR_LABELS`, `DEFAULT_COMBOBOX_LABELS`, `DEFAULT_COMMON_LABELS`, `DEFAULT_CURRENCY_LABELS`, `DEFAULT_DATE_PICKER_LABELS`, `DEFAULT_FILE_LABELS`, `DEFAULT_MULTI_SELECT_LABELS`, `DEFAULT_PICKER_SHEET_LABELS`, `DEFAULT_SWIPEABLE_ROW_LABELS`, `DEFAULT_TOP_BAR_LABELS`, `formatFileSize`, `missingKitLabels`, `UiKitProvider`, `useKitChartTooltipPlacement`, `useKitDateFormatter`, `useKitFileLabels`, `useKitLabelOverrides`, `useKitLabels`, `useKitLink`, `useKitLocale`, `useKitTableStorageKeyPrefix`, `useKitTimeZone`, `useKitWeekStart`, _`AppShellLabels`_, _`CalculatorLabels`_, _`ChartTooltipPlacement`_, _`ComboboxLabels`_, _`CommonLabels`_, _`CurrencyLabels`_, _`DatePickerLabels`_, _`FileLabels`_, _`KitDateFormatContext`_, _`KitDateFormatter`_, _`KitLinkComponent`_, _`KitLinkProps`_, _`LabelOverride`_, _`MultiSelectLabels`_, _`PickerSheetLabels`_, _`SwipeableRowLabels`_, _`TopBarLabels`_, _`UiKitLabelOverrides`_, _`UiKitLabels`_, _`UiKitProviderProps`_ |
656
661
  | `i18n/defaults` | `DEFAULT_UI_KIT_LABELS` |
657
662
  | `i18n/review` | `kitLabelStrings`, _`KitLabelStringsOptions`_ |
658
663
  | `i18n/languages` | `formatLocaleOf`, `KIT_LANGUAGES`, `languageOptions`, `loadUiKitLabels`, `peekUiKitLabels`, `resolveLanguage`, `useUiKitLabels`, _`KitLanguage`_, _`KitLanguageCode`_ |
@@ -748,7 +753,7 @@ re-slicing of it, never a second API.
748
753
  | `components/tree-view` | `TreeRow`, `TreeView`, _`TreeItemState`_, _`TreeNode`_, _`TreeRowProps`_, _`TreeViewProps`_ |
749
754
  | `components/description-list` | `DEFAULT_DESCRIPTION_LIST_LABELS`, `DescriptionItem`, `DescriptionList`, _`DescriptionItemProps`_, _`DescriptionListColumns`_, _`DescriptionListDensity`_, _`DescriptionListLabels`_, _`DescriptionListLayout`_, _`DescriptionListProps`_ |
750
755
  | `components/form-actions` | `DEFAULT_FORM_ACTIONS_LABELS`, `FormActions`, _`FormActionsAlign`_, _`FormActionsDestructive`_, _`FormActionsLabels`_, _`FormActionsPlacement`_, _`FormActionsProps`_, _`FormActionsStack`_, _`FormActionsStickyWithin`_, _`FormActionsSubmitProps`_, _`FormActionsSubmitShortcut`_, _`ResponsiveFormActionsPlacement`_ |
751
- | `components/write-lock` | `COMMIT_EXCEPT_BILLING`, `DEFAULT_WRITE_LOCK_LABELS`, `useWriteLock`, `writeLockFor`, `WriteLockProvider`, _`CommitScope`_, _`WriteLock`_, _`WriteLockHold`_, _`WriteLockKind`_, _`WriteLockLabels`_, _`WriteLockProviderProps`_ |
756
+ | `components/write-lock` | `COMMIT_EXCEPT_BILLING`, `COMMIT_EXCEPT_OFFLINE`, `DEFAULT_WRITE_LOCK_LABELS`, `OFFLINE_LOCK`, `useOfflineWriteLock`, `useWriteLock`, `writeLockFor`, `WriteLockProvider`, _`CommitScope`_, _`WriteLock`_, _`WriteLockHold`_, _`WriteLockKind`_, _`WriteLockLabels`_, _`WriteLockProviderProps`_ |
752
757
  | `components/account-chips` | `ACCOUNT_STATE_TONES`, `AccountRoleChip`, `AccountStateChip`, `dateColumn`, `DateMark`, `DEFAULT_ACCOUNT_STATE_LABELS`, `RoleChip`, _`AccountRoleChipProps`_, _`AccountState`_, _`AccountStateChipProps`_, _`AccountStateLabels`_, _`DateColumnOptions`_, _`DateMarkProps`_, _`RoleChipProps`_, _`RoleDefinition`_, _`RoleVocabulary`_ |
753
758
  | `components/server-wake` | `DEFAULT_SERVER_WAKE_LABELS`, `ServerWakeNotice`, `useServerWakeStage`, _`ServerWakeLabels`_, _`ServerWakeNoticeProps`_ |
754
759
  | `components/share-card` | `DEFAULT_SHARE_CARD_LABELS`, `ShareCard`, `ShareDialog`, `SharePanel`, _`ShareAddRequest`_, _`ShareCandidate`_, _`ShareCardLabels`_, _`ShareCardProps`_, _`ShareDialogProps`_, _`ShareGrantee`_, _`SharePanelProps`_, _`SharePendingGrant`_, _`ShareRole`_ |
@@ -778,6 +783,7 @@ re-slicing of it, never a second API.
778
783
  | `components/legal` | `DEFAULT_LEGAL_LABELS`, `LEGAL_HREFS`, `LegalFooter`, `LegalLayout`, `LegalLinks`, `legalOperatorText`, `LegalSection`, `useLegalLabels`, _`LegalDisclaimerSectionLabels`_, _`LegalFooterProps`_, _`LegalFramedSectionLabels`_, _`LegalLabels`_, _`LegalLayoutProps`_, _`LegalLink`_, _`LegalLinksProps`_, _`LegalOperator`_, _`LegalOperatorSectionLabels`_, _`LegalOperatorText`_, _`LegalPageKey`_, _`LegalSectionProps`_, _`LegalTextSectionLabels`_ |
779
784
  | `components/legal-page` | `LEGAL_SKELETON`, `LegalAcceptCheckbox`, `LegalKitSection`, `LegalPage`, _`LegalAcceptCheckboxProps`_, _`LegalDisclaimerVariant`_, _`LegalKitSectionKey`_, _`LegalKitSectionProps`_, _`LegalPageProps`_, _`LegalSectionOwner`_, _`LegalSkeletonEntry`_, _`LegalSkeletonPage`_ |
780
785
  | `components/appearance-labels` | `DEFAULT_APPEARANCE_LABELS`, `useAppearanceLabels`, _`AppearanceLabels`_ |
786
+ | `components/date-format-setting` | `DateFormatSetting`, _`DateFormatSettingProps`_ |
781
787
  | `components/row-actions` | `DEFAULT_ROW_ACTIONS_LABELS`, `RowActions`, `rowActionsColumn`, _`RowAction`_, _`RowActionList`_, _`RowActionsCollapse`_, _`RowActionsColumnOptions`_, _`RowActionsLabels`_, _`RowActionsProps`_, _`RowActionsSize`_, _`RowActionTone`_ |
782
788
  | `components/checkbox-group` | `CheckboxGroup`, _`CheckboxGroupLegendVisibility`_, _`CheckboxGroupOption`_, _`CheckboxGroupProps`_ |
783
789
  | `components/one-time-code-input` | `OneTimeCodeInput`, _`OneTimeCodeInputProps`_ |
@@ -795,7 +801,7 @@ re-slicing of it, never a second API.
795
801
  | `components/data-table-filters` | `dateFilter`, `decodeFilterValue`, `decodeFilterValueOfType`, `defaultFilterState`, `encodeFilterValue`, `filterHref`, `isFilterActive`, `numberFilter`, `resolveFilter`, `rowMatches`, `selectFilter`, `textFilter`, _`ColumnFilter`_, _`FilterValue`_ |
796
802
  | `components/data-table-pagination` | `PAGE_SIZE_OPTIONS`, `Pagination` |
797
803
  | `components/data-table-filter-popover` | `FilterPopover` |
798
- | `components/data-table` | `DataTable`, `DEFAULT_DATA_TABLE_SORT_LABELS`, _`DataTableCellProps`_, _`DataTableChrome`_, _`DataTableColumn`_, _`DataTableDensity`_, _`DataTableHeadProps`_, _`DataTableProps`_, _`DataTableRowAction`_, _`DataTableSortLabels`_, _`FilterState`_, _`MobileSwipeActions`_, _`ServerPagination`_ |
804
+ | `components/data-table` | `DataTable`, `DEFAULT_DATA_TABLE_SORT_LABELS`, _`DataTableCellProps`_, _`DataTableChrome`_, _`DataTableColumn`_, _`DataTableDensity`_, _`DataTableDesktopSwipeActions`_, _`DataTableHeadProps`_, _`DataTableProps`_, _`DataTableRowAction`_, _`DataTableSortLabels`_, _`FilterState`_, _`MobileSwipeActions`_, _`ServerPagination`_ |
799
805
  | `components/data-table-cells` | `booleanColumn`, `BooleanMark`, _`BooleanColumnOptions`_, _`BooleanMarkProps`_ |
800
806
  | `components/use-table-state` | `readTableUrlState`, `useTableUrlState`, _`TableUrlState`_, _`TableUrlStateProps`_, _`TableUrlSync`_, _`UseTableUrlStateOptions`_, _`UseTableUrlStateReturn`_ |
801
807
  | `components/translation-review-editor` | `TranslationReviewEditor`, _`TranslationReviewEditorProps`_ |
@@ -825,6 +831,7 @@ re-slicing of it, never a second API.
825
831
  | `shell/admin-menu` | `adminMenuEntry`, `AdminMenuRow`, `AdminWaitingBadge`, `useAdminMenuEntry`, _`AdminMenuEntryOptions`_, _`AdminMenuRowProps`_, _`AdminWaitingBadgeProps`_, _`UseAdminMenuEntryOptions`_ |
826
832
  | `shell/top-bar-actions` | `TOP_BAR_ACTION_ORDER`, `TopBarActions`, _`TopBarActionsProps`_ |
827
833
  | `shell/account-menu-language` | `AccountMenuLanguage`, _`AccountMenuLanguageOption`_, _`AccountMenuLanguageProps`_ |
834
+ | `shell/account-menu-text-size` | `AccountMenuTextSize`, _`AccountMenuTextSizeProps`_ |
828
835
  | `shell/tour-menu` | `TourMenu`, _`TourMenuProps`_, _`TourMenuTour`_ |
829
836
  | `shell/sync-status-indicator` | `syncState`, `SyncStatusIndicator`, _`SyncState`_, _`SyncStatus`_, _`SyncStatusIndicatorProps`_, _`SyncStatusScope`_ |
830
837
  | `shell/dev-switchers` | `DEV_ORIGINAL_ROLE_KEY`, `DevSwitchers`, _`DevSwitchersProps`_ |
@@ -870,7 +877,7 @@ re-slicing of it, never a second API.
870
877
  | `feedback/feedback-record` | `appendRework`, `attachmentName`, `FEEDBACK_ATTACHMENT_ACCEPT`, `FEEDBACK_AUTHOR_EDITABLE_STATUSES`, `FEEDBACK_AWAITING_STATUSES`, `FEEDBACK_COMMENT_MAX`, `FEEDBACK_DECIDED_STATUSES`, `FEEDBACK_PICKABLE_CATEGORIES`, `FEEDBACK_QUEUE_STATUSES`, `FEEDBACK_REPLY_MAX`, `FEEDBACK_REWORKABLE_STATUSES`, `feedbackAttachmentLine`, `feedbackReworkCount`, `isImageAttachment`, `replaceDescription`, `reworkCount`, `splitBodyAttachments`, `splitDescription`, _`FeedbackContext`_, _`FeedbackEnvironment`_, _`FeedbackRecord`_ |
871
878
  | `feedback/feedback-labels` | `DEFAULT_FEEDBACK_CATEGORY_LABELS`, `DEFAULT_FEEDBACK_STATUS_LABELS`, `DEFAULT_FEEDBACK_TOAST_LABELS`, `useFeedbackCategoryLabels`, `useFeedbackStatusLabels`, `useFeedbackToastLabels`, _`FeedbackCategoryLabels`_, _`FeedbackStatusLabels`_, _`FeedbackToastLabels`_ |
872
879
  | `feedback/feedback-status-undo` | `FEEDBACK_UNDO_DURATION`, `useFeedbackStatusUndo`, _`FeedbackStatusChange`_, _`FeedbackStatusMutate`_, _`FeedbackStatusPatch`_, _`FeedbackStatusRow`_, _`FeedbackStatusUndoOptions`_ |
873
- | `feedback/feedback-swipe` | `DEFAULT_FEEDBACK_SWIPE`, `FEEDBACK_SWIPE_ACTIONS`, `feedbackSwipePlan`, _`FeedbackSwipeAction`_, _`FeedbackSwipeBinding`_, _`FeedbackSwipeOptions`_ |
880
+ | `feedback/feedback-swipe` | `DEFAULT_FEEDBACK_SWIPE`, `FEEDBACK_SWIPE_ACTIONS`, `feedbackSwipeOmitShown`, `feedbackSwipePlan`, _`FeedbackSwipeAction`_, _`FeedbackSwipeBinding`_, _`FeedbackSwipeOptions`_ |
874
881
  | `feedback/feedback-menu` | `DEFAULT_FEEDBACK_MENU_LABELS`, `FeedbackMenu`, _`FeedbackMenuLabels`_, _`FeedbackMenuProps`_ |
875
882
  | `feedback/feedback-context` | `DEFAULT_FEEDBACK_CONTEXT_LABELS`, `feedbackContext`, `FeedbackContextBox`, _`FeedbackContextBoxProps`_, _`FeedbackContextInput`_, _`FeedbackContextLabels`_, _`FeedbackSubmitter`_ |
876
883
  | `feedback/feedback-capture` | `captureAppScreenshot`, _`CaptureAppScreenshotOptions`_ |
@@ -960,7 +967,7 @@ re-slicing of it, never a second API.
960
967
  | `admin/admin-area-labels` | `DEFAULT_ADMIN_AREA_LABELS`, `useAdminAreaLabels`, _`AdminAreaLabels`_, _`AdminSharedSection`_, _`AdminTileLabels`_ |
961
968
  | `admin/admin-sections` | `ADMIN_SECTIONS`, `adminSectionOrder`, `adminSections`, _`AdminKeptSection`_, _`AdminOwnSection`_, _`AdminSectionDefinition`_, _`AdminSectionOrderOptions`_, _`AdminSectionPlace`_, _`AdminSectionsOptions`_ |
962
969
  | `admin/admin-forbidden` | `AdminForbidden`, _`AdminForbiddenProps`_ |
963
- | `admin/admin-overview` | `AdminOverview`, `adminWaitingCount`, `AttentionTile`, `DEFAULT_ADMIN_ATTENTION_HREFS`, _`AdminAttention`_, _`AdminAttentionTile`_, _`AdminExtraTile`_, _`AdminOverviewProps`_, _`AttentionTileProps`_ |
970
+ | `admin/admin-overview` | `AdminOverview`, `adminWaitingCount`, `AttentionTile`, `DEFAULT_ADMIN_ATTENTION_HREFS`, _`AdminAttention`_, _`AdminAttentionTile`_, _`AdminExtraTile`_, _`AdminFeedbackTile`_, _`AdminFeedbackTileKey`_, _`AdminOverviewProps`_, _`AdminOwnTile`_, _`AttentionTileProps`_ |
964
971
  | `admin/admin-tool-links` | `AdminToolLinks`, _`AdminToolLink`_, _`AdminToolLinksProps`_ |
965
972
  | `admin/mail-test-card` | `DEFAULT_MAIL_TEST_CARD_LABELS`, `MAIL_TEST_ANCHOR`, `MailTestCard`, `maskMailAddress`, _`MailTestAnswer`_, _`MailTestCardLabels`_, _`MailTestCardProps`_ |
966
973
  | `admin/plan-change` | `DEFAULT_PLAN_CHANGE_LABELS`, `PlanChangeConfirm`, `planColumn`, `usePlanChangeResult`, `usePlanColumn`, _`PlanChange`_, _`PlanChangeConfirmProps`_, _`PlanChangeLabels`_, _`PlanChangeLine`_, _`PlanChangeOutcome`_, _`PlanChangeResultOptions`_, _`PlanChangeTone`_, _`PlanColumnOptions`_, _`PlanColumnUsage`_ |
@@ -994,11 +1001,15 @@ re-slicing of it, never a second API.
994
1001
  | Module | Exports |
995
1002
  |---|---|
996
1003
  | `notifications/notifications-labels` | `DEFAULT_NOTIFICATIONS_LABELS`, _`NotificationRegionLabels`_, _`NotificationsLabels`_ |
997
- | `notifications/notification-types` | _`DigestCadence`_, _`NotificationSettingsPatch`_, _`NotificationSwitches`_, _`NotificationTypeEntry`_, _`NotificationTypeGroup`_, _`NotificationTypesValue`_, _`PushDeviceItem`_ |
1004
+ | `notifications/notification-types` | _`DigestCadence`_, _`NotificationChannel`_, _`NotificationSettingsPatch`_, _`NotificationSwitches`_, _`NotificationTypeEntry`_, _`NotificationTypeGroup`_, _`NotificationTypesValue`_, _`PushDeviceItem`_ |
998
1005
  | `notifications/notification-types-setting` | `NotificationTypesSetting`, _`NotificationTypesSettingProps`_ |
999
1006
  | `notifications/quiet-hours-setting` | `QuietHoursSetting`, _`QuietHoursSettingProps`_ |
1000
1007
  | `notifications/time-zone-select` | `deviceTimeZone`, `timeZoneOptions`, `TimeZoneSelect`, _`TimeZoneSelectProps`_ |
1008
+ | `notifications/notification-inbox` | `INBOX_BADGE_INTERVAL`, `NotificationInbox`, _`NotificationInboxItem`_, _`NotificationInboxProps`_ |
1009
+ | `notifications/notification-bell` | `NotificationBell`, _`NotificationBellProps`_ |
1010
+ | `notifications/device-zone-report` | `useDeviceZoneReport`, _`DeviceZoneReportOptions`_ |
1001
1011
  | `notifications/use-push-device` | `notifyPushSubscriptionChanged`, `PUSH_SUBSCRIPTION_CHANGED`, `PushDeviceError`, `pushSupported`, `usePushDevice`, _`PushDevice`_, _`PushDeviceErrorCode`_, _`PushPermission`_, _`PushSubscriptionInput`_, _`UsePushDeviceOptions`_ |
1012
+ | `notifications/push-worker` | `handlePushSubscriptionChange`, `pushSubscriptionInput`, `resubscribeAfterChange`, _`PushReplaceInput`_, _`PushResubscribeOutcome`_, _`PushSubscriptionChangeEventLike`_, _`PushSubscriptionChangeOptions`_, _`PushWorkerScope`_ |
1002
1013
  | `notifications/push-device-setting` | `PushDeviceSetting`, _`PushDeviceAction`_, _`PushDeviceSettingProps`_ |
1003
1014
  | `notifications/push-devices-setting` | `describeUserAgent`, `PushDevicesSetting`, _`PushDevicesSettingProps`_ |
1004
1015
  | `notifications/unsubscribe-panel` | `UnsubscribePanel`, `unsubscribeTokenType`, _`UnsubscribePanelProps`_ |
@@ -21,6 +21,15 @@ export interface AdminTileLabels {
21
21
  users: string;
22
22
  /** `users_new_7d`, the line under the users tile. */
23
23
  usersNew: (count: number) => string;
24
+ /** 0.46 (Kurvenschmiede 1, admin §9.5): server-kit's `awaiting_verdict` as an `extra`
25
+ * tile — answered reports that wait on a verdict (IN_EVALUATION, NEEDS_LIVE_TEST). */
26
+ awaitingVerdict: string;
27
+ /** Its line under the count. */
28
+ awaitingVerdictHint: string;
29
+ /** 0.46: server-kit's `released_for_work` — the reports released for the work queue. */
30
+ releasedForWork: string;
31
+ /** Its line under the count. */
32
+ releasedForWorkHint: string;
24
33
  }
25
34
  export interface AdminAreaLabels {
26
35
  /** "Administration": the account menu's row, and the admin page's heading. */
@@ -20,7 +20,11 @@ const DEFAULT_ADMIN_AREA_LABELS = {
20
20
  deletionRequests: "Deletion requests",
21
21
  invitationsPending: "Pending invitations",
22
22
  users: "Users",
23
- usersNew: (count) => `${count} new in 7 days`
23
+ usersNew: (count) => `${count} new in 7 days`,
24
+ awaitingVerdict: "Awaiting a verdict",
25
+ awaitingVerdictHint: "Answered, and waiting for you or the reporter to confirm the fix",
26
+ releasedForWork: "Released for work",
27
+ releasedForWorkHint: "Ready, rework requested or in progress"
24
28
  },
25
29
  overviewError: "The overview couldn\u2019t be loaded.",
26
30
  retry: "Try again",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/admin-area-labels.ts"],"sourcesContent":["import { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport type { SettingsGroupLabels } from \"../settings/settings-labels\";\n\n/**\n * The `adminArea` namespace (0.42, docs/admin-harmonization.md §3.1): the kit's words of\n * the admin area every app shares — the account menu's \"Administration\" row, the 403\n * page, the shared sections' names and help lines, the overview's tiles and the Tools\n * list. A module of words with no component code, like every `*-labels.ts` (0.34, KS U8).\n * An app's own sections and tiles bring their own words.\n */\n\n/** The sections every app's admin area shares, in the template's order (§2.3). */\nexport type AdminSharedSection = \"overview\" | \"users\" | \"invitations\" | \"billing\" | \"activity\";\n\n/** The overview's shared tiles (§2.4, §6.1). */\nexport interface AdminTileLabels {\n /** `feedback_waiting`: OPEN reports, crash reports included (§6.1). */\n feedbackWaiting: string;\n /** `deletion_requests`. */\n deletionRequests: string;\n /** `invitations_pending`. */\n invitationsPending: string;\n /** `users_total`. */\n users: string;\n /** `users_new_7d`, the line under the users tile. */\n usersNew: (count: number) => string;\n}\n\nexport interface AdminAreaLabels {\n /** \"Administration\": the account menu's row, and the admin page's heading. */\n title: string;\n /** The row's waiting count, for a reader (\"Administration, 3 waiting\"); the eye sees\n * the number in the badge. */\n waiting: (count: number) => string;\n /**\n * 0.45 (admin §9.3.4): a button's whole name with its count — \"Platform, 2 waiting\" —\n * for an `IconButton` whose `badge` is an `AdminWaitingBadge`: the badge is decorative\n * there, so the name carries the count. `label` is the button's own name.\n */\n waitingLabel: (label: string, count: number) => string;\n /** The 403 page's heading (§2.2 decision 8). */\n forbiddenTitle: string;\n /**\n * Under it. Says only that THIS area is closed to the account — never that the person\n * administers nothing anywhere (§6.7: a kastlan superuser who isn't ADMIN in the acting\n * company gets this page).\n */\n forbiddenBody: (appName: string) => string;\n /** The button back to the app's start, unless the app names its own (`homeLabel`). */\n forbiddenHome: string;\n /** A reviewer's second way out, to the translations review. */\n forbiddenReviewer: string;\n /** The shared sections' names and help lines (§2.3 decision 11). */\n sections: Record<AdminSharedSection, SettingsGroupLabels>;\n /** The overview's tiles. */\n tiles: AdminTileLabels;\n /** 0.43: `AdminOverview`'s `error` — the counts could not be loaded. */\n overviewError: string;\n /** 0.43: its button, which calls `onRetry`. */\n retry: string;\n /** The Tools list's heading and name (§2.5 decision 16). */\n tools: string;\n}\n\nexport const DEFAULT_ADMIN_AREA_LABELS: AdminAreaLabels = {\n title: \"Administration\",\n waiting: (count) => `${count} waiting`,\n waitingLabel: (label, count) => `${label}, ${count} waiting`,\n forbiddenTitle: \"Only for administrators\",\n forbiddenBody: (appName) => `This is where administrators run ${appName}. Your account cannot open this page.`,\n forbiddenHome: \"Go to the start page\",\n forbiddenReviewer: \"Review translations\",\n sections: {\n overview: { title: \"Overview\", help: \"What is waiting, at a glance.\" },\n users: { title: \"Users\", help: \"Who has an account, and what each may do.\" },\n invitations: { title: \"Invitations\", help: \"Invite people by e-mail in their language; resend or revoke what’s still open.\" },\n billing: { title: \"Billing\", help: \"The plan, the payment and the invoices.\" },\n activity: { title: \"Activity\", help: \"What the admins did, newest first.\" },\n },\n tiles: {\n feedbackWaiting: \"To triage\",\n deletionRequests: \"Deletion requests\",\n invitationsPending: \"Pending invitations\",\n users: \"Users\",\n usersNew: (count) => `${count} new in 7 days`,\n },\n overviewError: \"The overview couldn’t be loaded.\",\n retry: \"Try again\",\n tools: \"Tools\",\n};\n\n/**\n * The `adminArea` namespace, resolved — English, then the provider, then `labels`. What\n * an app reads to hand {@link adminSections} its words from inside a component.\n */\nexport function useAdminAreaLabels(labels?: LabelOverride<AdminAreaLabels>): AdminAreaLabels {\n return useKitLabels(\"adminArea\", DEFAULT_ADMIN_AREA_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAwC;AAgE1C,MAAM,4BAA6C;AAAA,EACxD,OAAO;AAAA,EACP,SAAS,CAAC,UAAU,GAAG,KAAK;AAAA,EAC5B,cAAc,CAAC,OAAO,UAAU,GAAG,KAAK,KAAK,KAAK;AAAA,EAClD,gBAAgB;AAAA,EAChB,eAAe,CAAC,YAAY,oCAAoC,OAAO;AAAA,EACvE,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,UAAU;AAAA,IACR,UAAU,EAAE,OAAO,YAAY,MAAM,gCAAgC;AAAA,IACrE,OAAO,EAAE,OAAO,SAAS,MAAM,4CAA4C;AAAA,IAC3E,aAAa,EAAE,OAAO,eAAe,MAAM,sFAAiF;AAAA,IAC5H,SAAS,EAAE,OAAO,WAAW,MAAM,0CAA0C;AAAA,IAC7E,UAAU,EAAE,OAAO,YAAY,MAAM,qCAAqC;AAAA,EAC5E;AAAA,EACA,OAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB,OAAO;AAAA,IACP,UAAU,CAAC,UAAU,GAAG,KAAK;AAAA,EAC/B;AAAA,EACA,eAAe;AAAA,EACf,OAAO;AAAA,EACP,OAAO;AACT;AAMO,SAAS,mBAAmB,QAA0D;AAC3F,SAAO,aAAa,aAAa,2BAA2B,MAAM;AACpE;","names":[]}
1
+ {"version":3,"sources":["../../src/admin/admin-area-labels.ts"],"sourcesContent":["import { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport type { SettingsGroupLabels } from \"../settings/settings-labels\";\n\n/**\n * The `adminArea` namespace (0.42, docs/admin-harmonization.md §3.1): the kit's words of\n * the admin area every app shares — the account menu's \"Administration\" row, the 403\n * page, the shared sections' names and help lines, the overview's tiles and the Tools\n * list. A module of words with no component code, like every `*-labels.ts` (0.34, KS U8).\n * An app's own sections and tiles bring their own words.\n */\n\n/** The sections every app's admin area shares, in the template's order (§2.3). */\nexport type AdminSharedSection = \"overview\" | \"users\" | \"invitations\" | \"billing\" | \"activity\";\n\n/** The overview's shared tiles (§2.4, §6.1). */\nexport interface AdminTileLabels {\n /** `feedback_waiting`: OPEN reports, crash reports included (§6.1). */\n feedbackWaiting: string;\n /** `deletion_requests`. */\n deletionRequests: string;\n /** `invitations_pending`. */\n invitationsPending: string;\n /** `users_total`. */\n users: string;\n /** `users_new_7d`, the line under the users tile. */\n usersNew: (count: number) => string;\n /** 0.46 (Kurvenschmiede 1, admin §9.5): server-kit's `awaiting_verdict` as an `extra`\n * tile — answered reports that wait on a verdict (IN_EVALUATION, NEEDS_LIVE_TEST). */\n awaitingVerdict: string;\n /** Its line under the count. */\n awaitingVerdictHint: string;\n /** 0.46: server-kit's `released_for_work` — the reports released for the work queue. */\n releasedForWork: string;\n /** Its line under the count. */\n releasedForWorkHint: string;\n}\n\nexport interface AdminAreaLabels {\n /** \"Administration\": the account menu's row, and the admin page's heading. */\n title: string;\n /** The row's waiting count, for a reader (\"Administration, 3 waiting\"); the eye sees\n * the number in the badge. */\n waiting: (count: number) => string;\n /**\n * 0.45 (admin §9.3.4): a button's whole name with its count — \"Platform, 2 waiting\" —\n * for an `IconButton` whose `badge` is an `AdminWaitingBadge`: the badge is decorative\n * there, so the name carries the count. `label` is the button's own name.\n */\n waitingLabel: (label: string, count: number) => string;\n /** The 403 page's heading (§2.2 decision 8). */\n forbiddenTitle: string;\n /**\n * Under it. Says only that THIS area is closed to the account — never that the person\n * administers nothing anywhere (§6.7: a kastlan superuser who isn't ADMIN in the acting\n * company gets this page).\n */\n forbiddenBody: (appName: string) => string;\n /** The button back to the app's start, unless the app names its own (`homeLabel`). */\n forbiddenHome: string;\n /** A reviewer's second way out, to the translations review. */\n forbiddenReviewer: string;\n /** The shared sections' names and help lines (§2.3 decision 11). */\n sections: Record<AdminSharedSection, SettingsGroupLabels>;\n /** The overview's tiles. */\n tiles: AdminTileLabels;\n /** 0.43: `AdminOverview`'s `error` — the counts could not be loaded. */\n overviewError: string;\n /** 0.43: its button, which calls `onRetry`. */\n retry: string;\n /** The Tools list's heading and name (§2.5 decision 16). */\n tools: string;\n}\n\nexport const DEFAULT_ADMIN_AREA_LABELS: AdminAreaLabels = {\n title: \"Administration\",\n waiting: (count) => `${count} waiting`,\n waitingLabel: (label, count) => `${label}, ${count} waiting`,\n forbiddenTitle: \"Only for administrators\",\n forbiddenBody: (appName) => `This is where administrators run ${appName}. Your account cannot open this page.`,\n forbiddenHome: \"Go to the start page\",\n forbiddenReviewer: \"Review translations\",\n sections: {\n overview: { title: \"Overview\", help: \"What is waiting, at a glance.\" },\n users: { title: \"Users\", help: \"Who has an account, and what each may do.\" },\n invitations: { title: \"Invitations\", help: \"Invite people by e-mail in their language; resend or revoke what’s still open.\" },\n billing: { title: \"Billing\", help: \"The plan, the payment and the invoices.\" },\n activity: { title: \"Activity\", help: \"What the admins did, newest first.\" },\n },\n tiles: {\n feedbackWaiting: \"To triage\",\n deletionRequests: \"Deletion requests\",\n invitationsPending: \"Pending invitations\",\n users: \"Users\",\n usersNew: (count) => `${count} new in 7 days`,\n awaitingVerdict: \"Awaiting a verdict\",\n awaitingVerdictHint: \"Answered, and waiting for you or the reporter to confirm the fix\",\n releasedForWork: \"Released for work\",\n releasedForWorkHint: \"Ready, rework requested or in progress\",\n },\n overviewError: \"The overview couldn’t be loaded.\",\n retry: \"Try again\",\n tools: \"Tools\",\n};\n\n/**\n * The `adminArea` namespace, resolved — English, then the provider, then `labels`. What\n * an app reads to hand {@link adminSections} its words from inside a component.\n */\nexport function useAdminAreaLabels(labels?: LabelOverride<AdminAreaLabels>): AdminAreaLabels {\n return useKitLabels(\"adminArea\", DEFAULT_ADMIN_AREA_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAwC;AAyE1C,MAAM,4BAA6C;AAAA,EACxD,OAAO;AAAA,EACP,SAAS,CAAC,UAAU,GAAG,KAAK;AAAA,EAC5B,cAAc,CAAC,OAAO,UAAU,GAAG,KAAK,KAAK,KAAK;AAAA,EAClD,gBAAgB;AAAA,EAChB,eAAe,CAAC,YAAY,oCAAoC,OAAO;AAAA,EACvE,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,UAAU;AAAA,IACR,UAAU,EAAE,OAAO,YAAY,MAAM,gCAAgC;AAAA,IACrE,OAAO,EAAE,OAAO,SAAS,MAAM,4CAA4C;AAAA,IAC3E,aAAa,EAAE,OAAO,eAAe,MAAM,sFAAiF;AAAA,IAC5H,SAAS,EAAE,OAAO,WAAW,MAAM,0CAA0C;AAAA,IAC7E,UAAU,EAAE,OAAO,YAAY,MAAM,qCAAqC;AAAA,EAC5E;AAAA,EACA,OAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,oBAAoB;AAAA,IACpB,OAAO;AAAA,IACP,UAAU,CAAC,UAAU,GAAG,KAAK;AAAA,IAC7B,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,IACrB,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,EACvB;AAAA,EACA,eAAe;AAAA,EACf,OAAO;AAAA,EACP,OAAO;AACT;AAMO,SAAS,mBAAmB,QAA0D;AAC3F,SAAO,aAAa,aAAa,2BAA2B,MAAM;AACpE;","names":[]}
@@ -54,8 +54,16 @@ export interface AttentionTileProps extends Omit<StatTileProps, "href" | "onClic
54
54
  * is a quiet count, no link.
55
55
  */
56
56
  export declare function AttentionTile({ count, href, ...tile }: AttentionTileProps): import("react").JSX.Element;
57
+ /**
58
+ * 0.46 (Kurvenschmiede 1, admin §9.5): the `extra` keys server-kit's
59
+ * `feedback_attention` fills, whose tiles have the kit's words
60
+ * (`adminArea.tiles.awaitingVerdict`, `releasedForWork`, with a line each).
61
+ */
62
+ export type AdminFeedbackTileKey = "awaiting_verdict" | "released_for_work";
57
63
  /** One of the app's own tiles (§2.4 decision 13: keksdose's "Support waiting"). */
58
- export interface AdminExtraTile {
64
+ export type AdminExtraTile = AdminOwnTile | AdminFeedbackTile;
65
+ /** A tile of the app's own: it brings its words. */
66
+ export interface AdminOwnTile {
59
67
  /** Unique among the tiles; usually the `extra` key it shows. */
60
68
  key: string;
61
69
  label: ReactNode;
@@ -67,6 +75,13 @@ export interface AdminExtraTile {
67
75
  /** A line under the count. */
68
76
  description?: ReactNode;
69
77
  }
78
+ /** 0.46: a tile of server-kit's feedback counts — `label` and `description` default to
79
+ * the kit's words; an app's own still win. The kit's `description` fills in only when
80
+ * `label` is left out too: an app that names the tile gets no hint line it did not ask for. */
81
+ export interface AdminFeedbackTile extends Omit<AdminOwnTile, "key" | "label"> {
82
+ key: AdminFeedbackTileKey;
83
+ label?: ReactNode;
84
+ }
70
85
  export interface AdminOverviewProps {
71
86
  /** The server's answer; `undefined` while it loads (the grid shows its skeletons). */
72
87
  attention: AdminAttention | null | undefined;
@@ -60,9 +60,20 @@ function AdminOverview({
60
60
  description: attention && typeof attention.users_new_7d === "number" ? labels.tiles.usersNew(attention.users_new_7d) : void 0
61
61
  }
62
62
  ];
63
+ const feedbackWords = {
64
+ awaiting_verdict: { label: labels.tiles.awaitingVerdict, description: labels.tiles.awaitingVerdictHint },
65
+ released_for_work: { label: labels.tiles.releasedForWork, description: labels.tiles.releasedForWorkHint }
66
+ };
63
67
  const tiles = [
64
68
  ...shared.map((t) => ({ ...t, count: attention ? attention[t.key] : void 0, href: href[t.key] })),
65
- ...extraTiles
69
+ ...extraTiles.map((t) => {
70
+ const words = Object.prototype.hasOwnProperty.call(feedbackWords, t.key) ? feedbackWords[t.key] : void 0;
71
+ return words ? {
72
+ ...t,
73
+ label: t.label ?? words.label,
74
+ description: t.description ?? (t.label === void 0 ? words.description : void 0)
75
+ } : t;
76
+ })
66
77
  ].filter((t) => loading || t.count !== null && t.count !== void 0);
67
78
  if (!loading && tiles.length === 0) return error ? errorBox(true) : null;
68
79
  const grid = /* @__PURE__ */ jsx(StatTileGrid, { "data-slot": error ? void 0 : "admin-overview", loading, className: error ? void 0 : className, children: tiles.map((t) => /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/admin-overview.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { StatTile, StatTileGrid } from \"../components/stat-tile\";\nimport type { StatTileProps } from \"../components/stat-tile\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { useAdminAreaLabels } from \"./admin-area-labels\";\nimport type { AdminAreaLabels } from \"./admin-area-labels\";\n\n/**\n * The admin's start page (0.42.0, docs/admin-harmonization.md §2.4, §6.1–§6.5): a grid\n * of tiles, each a count of what is waiting and a link to where the work is, fed by one\n * server answer.\n */\n\n/**\n * `GET /admin/attention` (§2.4 decision 14, §6.4) — server-kit's `AdminAttention`\n * (`eifi1_server_kit.user_admin.attention`), as the API sends it.\n *\n * Each count is `null` where the app has no such thing or this viewer cannot act on it,\n * and every count is `null` in a demo session. Demo accounts never count. `extra` holds\n * the app's own counts, keyed as its overview names them (keksdose `support_waiting`,\n * Kurvenschmiede `released_for_work`).\n */\nexport interface AdminAttention {\n /** \"To triage\": OPEN reports, crash reports included (§6.1). */\n feedback_waiting: number | null;\n /** Accounts that asked to be deleted — only where this viewer acts on them (§6.2). */\n deletion_requests: number | null;\n invitations_pending: number | null;\n users_total: number | null;\n /** Accounts made in the last seven days. */\n users_new_7d: number | null;\n extra: Record<string, number | null>;\n}\n\n/** The shared counts that are a tile of their own (`users_new_7d` is the users tile's line). */\nexport type AdminAttentionTile = \"feedback_waiting\" | \"deletion_requests\" | \"invitations_pending\" | \"users_total\";\n\n/** Where each shared tile links when the app names nothing else (§2.4 decision 13). */\nexport const DEFAULT_ADMIN_ATTENTION_HREFS: Readonly<Record<AdminAttentionTile, string>> = Object.freeze({\n feedback_waiting: \"/feedback\",\n deletion_requests: \"/admin/users?state=deletion_requested\",\n invitations_pending: \"/admin/invitations\",\n users_total: \"/admin/users\",\n});\n\n/**\n * The account menu's badge (§2.4 decision 15, §6.3): the sum of the counts that are work\n * waiting on THIS admin — each app names its own (keksdose `[\"feedback_waiting\",\n * \"support_waiting\", \"awaiting_verdict\"]`, kastlan `[\"feedback_waiting\"]`, Kurvenschmiede\n * `[\"feedback_waiting\", \"deletion_requests\"]`). A name is a shared count or an `extra`\n * key; a `null` or missing count adds nothing. Invitations and users never belong in it.\n * `null` while there is no answer yet, so the badge shows nothing rather than a 0.\n */\nexport function adminWaitingCount(\n attention: AdminAttention | null | undefined,\n counts: readonly (AdminAttentionTile | (string & {}))[],\n): number | null {\n if (!attention) return null;\n let sum = 0;\n for (const key of counts) {\n const shared = (attention as unknown as Record<string, unknown>)[key];\n const value = typeof shared === \"number\" ? shared : attention.extra?.[key];\n if (typeof value === \"number\" && value > 0) sum += value;\n }\n return sum;\n}\n\nexport interface AttentionTileProps extends Omit<StatTileProps, \"href\" | \"onClick\" | \"value\"> {\n /** The count. `null` shows the tile's dash; {@link AdminOverview} leaves such a tile out. */\n count: number | null | undefined;\n /** Where the work is — the tile is a link to it. Since 0.43 it may be left out: a\n * quiet count, no link (Kurvenschmiede). */\n href?: string;\n}\n\n/**\n * One tile of the overview: a {@link StatTile} that is a LINK to where the work is, its\n * count the headline. The whole tile is the link, named by its label; route it through\n * `renderLink` or the provider's link component, as any StatTile. Without `href` (0.43) it\n * is a quiet count, no link.\n */\nexport function AttentionTile({ count, href, ...tile }: AttentionTileProps) {\n return <StatTile data-slot=\"attention-tile\" {...tile} value={count ?? null} href={href} />;\n}\n\n/** One of the app's own tiles (§2.4 decision 13: keksdose's \"Support waiting\"). */\nexport interface AdminExtraTile {\n /** Unique among the tiles; usually the `extra` key it shows. */\n key: string;\n label: ReactNode;\n /** `null` or `undefined` leaves the tile out, as for a shared one. */\n count: number | null | undefined;\n /** Where the work is. Left out (0.43, Kurvenschmiede): a quiet count with no link —\n * a number worth seeing that has no page of its own. */\n href?: string;\n /** A line under the count. */\n description?: ReactNode;\n}\n\nexport interface AdminOverviewProps {\n /** The server's answer; `undefined` while it loads (the grid shows its skeletons). */\n attention: AdminAttention | null | undefined;\n /**\n * Where the shared tiles link, over {@link DEFAULT_ADMIN_ATTENTION_HREFS} — the\n * inbox's address filtered to OPEN (§6.1) is the app's own:\n * `{ feedback_waiting: \"/feedback?status=OPEN\" }`.\n */\n hrefs?: Partial<Record<AdminAttentionTile, string>>;\n /** The app's own tiles, after the shared ones, in this order. */\n extraTiles?: readonly AdminExtraTile[];\n /** Passed to every tile — `renderLink` for the router, `size`, `variant`. */\n tileProps?: Pick<StatTileProps, \"renderLink\" | \"size\" | \"variant\" | \"locale\">;\n /**\n * 0.43 (keksdose): the answer failed — the query's `error`, or `true`. Anything truthy\n * shows `adminArea.overviewError` in a danger box, with \"Try again\" when\n * {@link onRetry} is given. A failed poll keeps the last good tiles: with `attention`\n * still there (a query keeps its last data), the box shows ALONGSIDE them, above, not\n * instead (build review 0.43, keksdose N7) — pass `attention.data` as it is. Without\n * an answer yet, the box alone. Without `error`: `undefined` is the skeletons and\n * `null` nothing, as before.\n */\n error?: unknown;\n /** \"Try again\" in the error box — the query's `refetch`. */\n onRetry?: () => void;\n /** Prop > `<UiKitProvider labels={{ adminArea }}>` > English. */\n labels?: LabelOverride<AdminAreaLabels>;\n className?: string;\n}\n\n/**\n * The overview section (§2.4): the shared tiles in their order — To triage, Deletion\n * requests, Pending invitations, Users (with the new accounts of the last 7 days under\n * the total) — then the app's `extraTiles`. A tile whose count is `null` is left out,\n * so each app shows only what it has; a `0` is a quiet tile, \"nothing waiting\".\n *\n * The overview is the admin area's landing page, standalone: it is NOT wrapped in a\n * `SettingsSection` (`adminSections` gives it no entry; render it as the page's body, as\n * Kurvenschmiede's `OverviewPanel` does). keksdose links its metrics from here with at\n * most two tiles (§6.5).\n */\nexport function AdminOverview({\n attention,\n hrefs,\n extraTiles = [],\n tileProps,\n error,\n onRetry,\n labels: labelsProp,\n className,\n}: AdminOverviewProps) {\n const labels = useAdminAreaLabels(labelsProp);\n /** The error box: on its own (`alone`) it is the overview's root. */\n const errorBox = (alone: boolean) => (\n <AlertBanner\n data-slot={alone ? \"admin-overview\" : undefined}\n data-state={alone ? \"error\" : undefined}\n variant=\"box\"\n tone=\"danger\"\n className={alone ? className : undefined}\n action={\n onRetry ? (\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={() => onRetry()}>\n {labels.retry}\n </Button>\n ) : undefined\n }\n >\n {labels.overviewError}\n </AlertBanner>\n );\n // Nothing good to keep: the box alone.\n if (error && !attention) return errorBox(true);\n const href = { ...DEFAULT_ADMIN_ATTENTION_HREFS, ...hrefs };\n const loading = attention === undefined;\n const shared: { key: AdminAttentionTile; label: string; description?: ReactNode }[] = [\n { key: \"feedback_waiting\", label: labels.tiles.feedbackWaiting },\n { key: \"deletion_requests\", label: labels.tiles.deletionRequests },\n { key: \"invitations_pending\", label: labels.tiles.invitationsPending },\n {\n key: \"users_total\",\n label: labels.tiles.users,\n description:\n attention && typeof attention.users_new_7d === \"number\" ? labels.tiles.usersNew(attention.users_new_7d) : undefined,\n },\n ];\n const tiles = [\n ...shared.map((t) => ({ ...t, count: attention ? attention[t.key] : undefined, href: href[t.key] })),\n ...extraTiles,\n ].filter((t) => loading || (t.count !== null && t.count !== undefined));\n if (!loading && tiles.length === 0) return error ? errorBox(true) : null;\n const grid = (\n <StatTileGrid data-slot={error ? undefined : \"admin-overview\"} loading={loading} className={error ? undefined : className}>\n {tiles.map((t) => (\n <AttentionTile\n key={t.key}\n {...tileProps}\n data-attention={t.key}\n label={t.label}\n count={t.count}\n href={t.href}\n description={t.description}\n />\n ))}\n </StatTileGrid>\n );\n if (!error) return grid;\n // A failed poll: the error above the last good tiles.\n return (\n <div data-slot=\"admin-overview\" data-state=\"error\" className={cn(\"space-y-4\", className)}>\n {errorBox(false)}\n {grid}\n </div>\n );\n}\n"],"mappings":";AAqFS,cA8HL,YA9HK;AApFT,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,UAAU,oBAAoB;AAGvC,SAAS,UAAU;AACnB,SAAS,0BAA0B;AAkC5B,MAAM,gCAA8E,OAAO,OAAO;AAAA,EACvG,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,aAAa;AACf,CAAC;AAUM,SAAS,kBACd,WACA,QACe;AACf,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI,MAAM;AACV,aAAW,OAAO,QAAQ;AACxB,UAAM,SAAU,UAAiD,GAAG;AACpE,UAAM,QAAQ,OAAO,WAAW,WAAW,SAAS,UAAU,QAAQ,GAAG;AACzE,QAAI,OAAO,UAAU,YAAY,QAAQ,EAAG,QAAO;AAAA,EACrD;AACA,SAAO;AACT;AAgBO,SAAS,cAAc,EAAE,OAAO,MAAM,GAAG,KAAK,GAAuB;AAC1E,SAAO,oBAAC,YAAS,aAAU,kBAAkB,GAAG,MAAM,OAAO,SAAS,MAAM,MAAY;AAC1F;AAyDO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,aAAa,CAAC;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,GAAuB;AACrB,QAAM,SAAS,mBAAmB,UAAU;AAE5C,QAAM,WAAW,CAAC,UAChB;AAAA,IAAC;AAAA;AAAA,MACC,aAAW,QAAQ,mBAAmB;AAAA,MACtC,cAAY,QAAQ,UAAU;AAAA,MAC9B,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,WAAW,QAAQ,YAAY;AAAA,MAC/B,QACE,UACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,MAAK,MAAK,SAAS,MAAM,QAAQ,GACxE,iBAAO,OACV,IACE;AAAA,MAGL,iBAAO;AAAA;AAAA,EACV;AAGF,MAAI,SAAS,CAAC,UAAW,QAAO,SAAS,IAAI;AAC7C,QAAM,OAAO,EAAE,GAAG,+BAA+B,GAAG,MAAM;AAC1D,QAAM,UAAU,cAAc;AAC9B,QAAM,SAAgF;AAAA,IACpF,EAAE,KAAK,oBAAoB,OAAO,OAAO,MAAM,gBAAgB;AAAA,IAC/D,EAAE,KAAK,qBAAqB,OAAO,OAAO,MAAM,iBAAiB;AAAA,IACjE,EAAE,KAAK,uBAAuB,OAAO,OAAO,MAAM,mBAAmB;AAAA,IACrE;AAAA,MACE,KAAK;AAAA,MACL,OAAO,OAAO,MAAM;AAAA,MACpB,aACE,aAAa,OAAO,UAAU,iBAAiB,WAAW,OAAO,MAAM,SAAS,UAAU,YAAY,IAAI;AAAA,IAC9G;AAAA,EACF;AACA,QAAM,QAAQ;AAAA,IACZ,GAAG,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,YAAY,UAAU,EAAE,GAAG,IAAI,QAAW,MAAM,KAAK,EAAE,GAAG,EAAE,EAAE;AAAA,IACnG,GAAG;AAAA,EACL,EAAE,OAAO,CAAC,MAAM,WAAY,EAAE,UAAU,QAAQ,EAAE,UAAU,MAAU;AACtE,MAAI,CAAC,WAAW,MAAM,WAAW,EAAG,QAAO,QAAQ,SAAS,IAAI,IAAI;AACpE,QAAM,OACJ,oBAAC,gBAAa,aAAW,QAAQ,SAAY,kBAAkB,SAAkB,WAAW,QAAQ,SAAY,WAC7G,gBAAM,IAAI,CAAC,MACV;AAAA,IAAC;AAAA;AAAA,MAEE,GAAG;AAAA,MACJ,kBAAgB,EAAE;AAAA,MAClB,OAAO,EAAE;AAAA,MACT,OAAO,EAAE;AAAA,MACT,MAAM,EAAE;AAAA,MACR,aAAa,EAAE;AAAA;AAAA,IANV,EAAE;AAAA,EAOT,CACD,GACH;AAEF,MAAI,CAAC,MAAO,QAAO;AAEnB,SACE,qBAAC,SAAI,aAAU,kBAAiB,cAAW,SAAQ,WAAW,GAAG,aAAa,SAAS,GACpF;AAAA,aAAS,KAAK;AAAA,IACd;AAAA,KACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/admin/admin-overview.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { StatTile, StatTileGrid } from \"../components/stat-tile\";\nimport type { StatTileProps } from \"../components/stat-tile\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { cn } from \"../lib/cn\";\nimport { useAdminAreaLabels } from \"./admin-area-labels\";\nimport type { AdminAreaLabels } from \"./admin-area-labels\";\n\n/**\n * The admin's start page (0.42.0, docs/admin-harmonization.md §2.4, §6.1–§6.5): a grid\n * of tiles, each a count of what is waiting and a link to where the work is, fed by one\n * server answer.\n */\n\n/**\n * `GET /admin/attention` (§2.4 decision 14, §6.4) — server-kit's `AdminAttention`\n * (`eifi1_server_kit.user_admin.attention`), as the API sends it.\n *\n * Each count is `null` where the app has no such thing or this viewer cannot act on it,\n * and every count is `null` in a demo session. Demo accounts never count. `extra` holds\n * the app's own counts, keyed as its overview names them (keksdose `support_waiting`,\n * Kurvenschmiede `released_for_work`).\n */\nexport interface AdminAttention {\n /** \"To triage\": OPEN reports, crash reports included (§6.1). */\n feedback_waiting: number | null;\n /** Accounts that asked to be deleted — only where this viewer acts on them (§6.2). */\n deletion_requests: number | null;\n invitations_pending: number | null;\n users_total: number | null;\n /** Accounts made in the last seven days. */\n users_new_7d: number | null;\n extra: Record<string, number | null>;\n}\n\n/** The shared counts that are a tile of their own (`users_new_7d` is the users tile's line). */\nexport type AdminAttentionTile = \"feedback_waiting\" | \"deletion_requests\" | \"invitations_pending\" | \"users_total\";\n\n/** Where each shared tile links when the app names nothing else (§2.4 decision 13). */\nexport const DEFAULT_ADMIN_ATTENTION_HREFS: Readonly<Record<AdminAttentionTile, string>> = Object.freeze({\n feedback_waiting: \"/feedback\",\n deletion_requests: \"/admin/users?state=deletion_requested\",\n invitations_pending: \"/admin/invitations\",\n users_total: \"/admin/users\",\n});\n\n/**\n * The account menu's badge (§2.4 decision 15, §6.3): the sum of the counts that are work\n * waiting on THIS admin — each app names its own (keksdose `[\"feedback_waiting\",\n * \"support_waiting\", \"awaiting_verdict\"]`, kastlan `[\"feedback_waiting\"]`, Kurvenschmiede\n * `[\"feedback_waiting\", \"deletion_requests\"]`). A name is a shared count or an `extra`\n * key; a `null` or missing count adds nothing. Invitations and users never belong in it.\n * `null` while there is no answer yet, so the badge shows nothing rather than a 0.\n */\nexport function adminWaitingCount(\n attention: AdminAttention | null | undefined,\n counts: readonly (AdminAttentionTile | (string & {}))[],\n): number | null {\n if (!attention) return null;\n let sum = 0;\n for (const key of counts) {\n const shared = (attention as unknown as Record<string, unknown>)[key];\n const value = typeof shared === \"number\" ? shared : attention.extra?.[key];\n if (typeof value === \"number\" && value > 0) sum += value;\n }\n return sum;\n}\n\nexport interface AttentionTileProps extends Omit<StatTileProps, \"href\" | \"onClick\" | \"value\"> {\n /** The count. `null` shows the tile's dash; {@link AdminOverview} leaves such a tile out. */\n count: number | null | undefined;\n /** Where the work is — the tile is a link to it. Since 0.43 it may be left out: a\n * quiet count, no link (Kurvenschmiede). */\n href?: string;\n}\n\n/**\n * One tile of the overview: a {@link StatTile} that is a LINK to where the work is, its\n * count the headline. The whole tile is the link, named by its label; route it through\n * `renderLink` or the provider's link component, as any StatTile. Without `href` (0.43) it\n * is a quiet count, no link.\n */\nexport function AttentionTile({ count, href, ...tile }: AttentionTileProps) {\n return <StatTile data-slot=\"attention-tile\" {...tile} value={count ?? null} href={href} />;\n}\n\n/**\n * 0.46 (Kurvenschmiede 1, admin §9.5): the `extra` keys server-kit's\n * `feedback_attention` fills, whose tiles have the kit's words\n * (`adminArea.tiles.awaitingVerdict`, `releasedForWork`, with a line each).\n */\nexport type AdminFeedbackTileKey = \"awaiting_verdict\" | \"released_for_work\";\n\n/** One of the app's own tiles (§2.4 decision 13: keksdose's \"Support waiting\"). */\nexport type AdminExtraTile = AdminOwnTile | AdminFeedbackTile;\n\n/** A tile of the app's own: it brings its words. */\nexport interface AdminOwnTile {\n /** Unique among the tiles; usually the `extra` key it shows. */\n key: string;\n label: ReactNode;\n /** `null` or `undefined` leaves the tile out, as for a shared one. */\n count: number | null | undefined;\n /** Where the work is. Left out (0.43, Kurvenschmiede): a quiet count with no link —\n * a number worth seeing that has no page of its own. */\n href?: string;\n /** A line under the count. */\n description?: ReactNode;\n}\n\n/** 0.46: a tile of server-kit's feedback counts — `label` and `description` default to\n * the kit's words; an app's own still win. The kit's `description` fills in only when\n * `label` is left out too: an app that names the tile gets no hint line it did not ask for. */\nexport interface AdminFeedbackTile extends Omit<AdminOwnTile, \"key\" | \"label\"> {\n key: AdminFeedbackTileKey;\n label?: ReactNode;\n}\n\nexport interface AdminOverviewProps {\n /** The server's answer; `undefined` while it loads (the grid shows its skeletons). */\n attention: AdminAttention | null | undefined;\n /**\n * Where the shared tiles link, over {@link DEFAULT_ADMIN_ATTENTION_HREFS} — the\n * inbox's address filtered to OPEN (§6.1) is the app's own:\n * `{ feedback_waiting: \"/feedback?status=OPEN\" }`.\n */\n hrefs?: Partial<Record<AdminAttentionTile, string>>;\n /** The app's own tiles, after the shared ones, in this order. */\n extraTiles?: readonly AdminExtraTile[];\n /** Passed to every tile — `renderLink` for the router, `size`, `variant`. */\n tileProps?: Pick<StatTileProps, \"renderLink\" | \"size\" | \"variant\" | \"locale\">;\n /**\n * 0.43 (keksdose): the answer failed — the query's `error`, or `true`. Anything truthy\n * shows `adminArea.overviewError` in a danger box, with \"Try again\" when\n * {@link onRetry} is given. A failed poll keeps the last good tiles: with `attention`\n * still there (a query keeps its last data), the box shows ALONGSIDE them, above, not\n * instead (build review 0.43, keksdose N7) — pass `attention.data` as it is. Without\n * an answer yet, the box alone. Without `error`: `undefined` is the skeletons and\n * `null` nothing, as before.\n */\n error?: unknown;\n /** \"Try again\" in the error box — the query's `refetch`. */\n onRetry?: () => void;\n /** Prop > `<UiKitProvider labels={{ adminArea }}>` > English. */\n labels?: LabelOverride<AdminAreaLabels>;\n className?: string;\n}\n\n/**\n * The overview section (§2.4): the shared tiles in their order — To triage, Deletion\n * requests, Pending invitations, Users (with the new accounts of the last 7 days under\n * the total) — then the app's `extraTiles`. A tile whose count is `null` is left out,\n * so each app shows only what it has; a `0` is a quiet tile, \"nothing waiting\".\n *\n * The overview is the admin area's landing page, standalone: it is NOT wrapped in a\n * `SettingsSection` (`adminSections` gives it no entry; render it as the page's body, as\n * Kurvenschmiede's `OverviewPanel` does). keksdose links its metrics from here with at\n * most two tiles (§6.5).\n */\nexport function AdminOverview({\n attention,\n hrefs,\n extraTiles = [],\n tileProps,\n error,\n onRetry,\n labels: labelsProp,\n className,\n}: AdminOverviewProps) {\n const labels = useAdminAreaLabels(labelsProp);\n /** The error box: on its own (`alone`) it is the overview's root. */\n const errorBox = (alone: boolean) => (\n <AlertBanner\n data-slot={alone ? \"admin-overview\" : undefined}\n data-state={alone ? \"error\" : undefined}\n variant=\"box\"\n tone=\"danger\"\n className={alone ? className : undefined}\n action={\n onRetry ? (\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={() => onRetry()}>\n {labels.retry}\n </Button>\n ) : undefined\n }\n >\n {labels.overviewError}\n </AlertBanner>\n );\n // Nothing good to keep: the box alone.\n if (error && !attention) return errorBox(true);\n const href = { ...DEFAULT_ADMIN_ATTENTION_HREFS, ...hrefs };\n const loading = attention === undefined;\n const shared: { key: AdminAttentionTile; label: string; description?: ReactNode }[] = [\n { key: \"feedback_waiting\", label: labels.tiles.feedbackWaiting },\n { key: \"deletion_requests\", label: labels.tiles.deletionRequests },\n { key: \"invitations_pending\", label: labels.tiles.invitationsPending },\n {\n key: \"users_total\",\n label: labels.tiles.users,\n description:\n attention && typeof attention.users_new_7d === \"number\" ? labels.tiles.usersNew(attention.users_new_7d) : undefined,\n },\n ];\n const feedbackWords: Record<AdminFeedbackTileKey, { label: string; description: string }> = {\n awaiting_verdict: { label: labels.tiles.awaitingVerdict, description: labels.tiles.awaitingVerdictHint },\n released_for_work: { label: labels.tiles.releasedForWork, description: labels.tiles.releasedForWorkHint },\n };\n const tiles = [\n ...shared.map((t) => ({ ...t, count: attention ? attention[t.key] : undefined, href: href[t.key] })),\n ...extraTiles.map((t) => {\n const words = Object.prototype.hasOwnProperty.call(feedbackWords, t.key)\n ? feedbackWords[t.key as AdminFeedbackTileKey]\n : undefined;\n // The kit's hint comes with the kit's label only: an app that names the tile\n // itself gets no line it did not ask for (keksdose S5, round 0.46).\n return words\n ? {\n ...t,\n label: t.label ?? words.label,\n description: t.description ?? (t.label === undefined ? words.description : undefined),\n }\n : t;\n }),\n ].filter((t) => loading || (t.count !== null && t.count !== undefined));\n if (!loading && tiles.length === 0) return error ? errorBox(true) : null;\n const grid = (\n <StatTileGrid data-slot={error ? undefined : \"admin-overview\"} loading={loading} className={error ? undefined : className}>\n {tiles.map((t) => (\n <AttentionTile\n key={t.key}\n {...tileProps}\n data-attention={t.key}\n label={t.label}\n count={t.count}\n href={t.href}\n description={t.description}\n />\n ))}\n </StatTileGrid>\n );\n if (!error) return grid;\n // A failed poll: the error above the last good tiles.\n return (\n <div data-slot=\"admin-overview\" data-state=\"error\" className={cn(\"space-y-4\", className)}>\n {errorBox(false)}\n {grid}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -37,8 +37,9 @@ export interface MailTestAnswer {
37
37
  export declare const MAIL_TEST_ANCHOR = "admin-mail-test";
38
38
  export interface MailTestCardProps extends Omit<SettingsSectionProps, "anchor" | "title" | "description" | "children"> {
39
39
  /** The installation's transport, as the admin page has it (`mail_backend`): the chip
40
- * before a send. After one, the answer's. */
41
- mailBackend: string;
40
+ * before a send. After one, the answer's. Optional (0.46): without it the chip shows
41
+ * from the first answer on, and an app needs no query only for the chip. */
42
+ mailBackend?: string;
42
43
  /** The app's `POST /admin/mail/test` (kastlan `/platform/mail/test`), no body: it
43
44
  * awaits the app's own send and answers `{sent, mail_backend}`. A rejection is read
44
45
  * for a 429 (the limiter); anything else is "could not send". */
@@ -28,6 +28,7 @@ function MailTestCard({
28
28
  const labels = useKitLabels("mailTestCard", DEFAULT_MAIL_TEST_CARD_LABELS, labelsProp);
29
29
  const [busy, setBusy] = useState(false);
30
30
  const [outcome, setOutcome] = useState(null);
31
+ const [answered, setAnswered] = useState(void 0);
31
32
  const run = async () => {
32
33
  if (busy) return;
33
34
  setBusy(true);
@@ -35,6 +36,7 @@ function MailTestCard({
35
36
  try {
36
37
  const answer = await send();
37
38
  const backend2 = answer.mail_backend;
39
+ if (backend2) setAnswered(backend2);
38
40
  if (backend2 === "console") setOutcome({ kind: "console", backend: backend2 });
39
41
  else setOutcome(answer.sent ? { kind: "sent", backend: backend2 } : { kind: "failed", backend: backend2 });
40
42
  } catch (error) {
@@ -43,7 +45,7 @@ function MailTestCard({
43
45
  setBusy(false);
44
46
  }
45
47
  };
46
- const backend = outcome && "backend" in outcome && outcome.backend ? outcome.backend : mailBackend;
48
+ const backend = answered ?? mailBackend;
47
49
  return /* @__PURE__ */ jsx(
48
50
  SettingsSection,
49
51
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/mail-test-card.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Send } from \"lucide-react\";\n\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { isRateLimited, retryAfterSeconds } from \"../auth/auth-errors\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { Chip } from \"../components/chip\";\nimport { SettingsSection, type SettingsSectionProps } from \"../settings/settings-section\";\nimport { DEFAULT_MAIL_TEST_CARD_LABELS } from \"./mail-test-card-labels\";\n\n/* ── Labels ──────────────────────────────────────────────────────────────── */\n\n/** Every string {@link MailTestCard} renders — the `mailTestCard` namespace of\n * `<UiKitProvider labels>`, overridable per instance through `labels`\n * (docs/admin-harmonization.md §9.2.4). */\nexport interface MailTestCardLabels {\n /** The card's title, and the app's catalogue entry's. */\n title: string;\n /** The sentence under it. */\n description: string;\n /** The transport's chip: \"Transport: resend\". `backend` is the server's `mail_backend`. */\n transport: (backend: string) => string;\n /** The button. */\n action: string;\n /** The answer said \"sent\" through a real transport. `address` is masked (\"a…@example.com\"). */\n sent: (address: string) => string;\n /** `mail_backend` is `console`: said whatever `sent` is — a console answers `true`. */\n console: string;\n /** Not sent, or the request failed. The provider's error stays in the server log. */\n failed: string;\n /** HTTP 429 — the app's limiter (server-kit's `MAIL_TEST_LIMIT`, 3 per 10 minutes);\n * `seconds` from `Retry-After` when the response has one. */\n rateLimited: (seconds?: number) => string;\n /** For the app's catalogue entry (`keywords`); the card never reads it. */\n keywords: string;\n}\n\n// Defined in ./mail-test-card-labels, which holds no component code.\nexport { DEFAULT_MAIL_TEST_CARD_LABELS };\n\n/* ── The card ────────────────────────────────────────────────────────────── */\n\n/** The answer of `POST /admin/mail/test` — server-kit 0.15's `MailTestResult`. */\nexport interface MailTestAnswer {\n sent: boolean;\n /** The transport that took it: `console`, `resend`, `smtp`, … */\n mail_backend: string;\n}\n\n/** The card's default DOM id: its catalogue entry's `anchor`, the `?focus=` target. */\nexport const MAIL_TEST_ANCHOR = \"admin-mail-test\";\n\nexport interface MailTestCardProps\n extends Omit<SettingsSectionProps, \"anchor\" | \"title\" | \"description\" | \"children\"> {\n /** The installation's transport, as the admin page has it (`mail_backend`): the chip\n * before a send. After one, the answer's. */\n mailBackend: string;\n /** The app's `POST /admin/mail/test` (kastlan `/platform/mail/test`), no body: it\n * awaits the app's own send and answers `{sent, mail_backend}`. A rejection is read\n * for a 429 (the limiter); anything else is \"could not send\". */\n send: () => Promise<MailTestAnswer>;\n /** The signed-in admin's own address — where the mail goes. Shown masked. */\n address: string;\n /** Default {@link MAIL_TEST_ANCHOR}; the app's catalogue entry names the same. */\n anchor?: string;\n /** The app's sentence after the kit's description. */\n description?: ReactNode;\n labels?: LabelOverride<MailTestCardLabels>;\n}\n\n/**\n * An address with its local part masked to its first character: \"ada@example.com\" →\n * \"a…@example.com\". The domain stays, so the admin sees which mailbox to open.\n */\nexport function maskMailAddress(address: string): string {\n const text = address.trim();\n const at = text.lastIndexOf(\"@\");\n if (at <= 0) return text === \"\" ? \"\" : `${Array.from(text)[0]}…`;\n return `${Array.from(text.slice(0, at))[0]}…${text.slice(at)}`;\n}\n\ntype Outcome =\n | { kind: \"sent\"; backend: string }\n | { kind: \"console\"; backend: string }\n | { kind: \"failed\"; backend?: string }\n | { kind: \"limited\"; seconds?: number };\n\n/**\n * \"Send a test mail to me\" (docs/admin-harmonization.md §9.2): a `SettingsSection` with a\n * sentence, the transport as a chip, the button and the result line. One mail goes to the\n * signed-in admin's own address, never another, through the app's own send.\n *\n * **The result line reads `mail_backend` first**: with `console` it says \"nothing was sent\"\n * whatever `sent` is, since every console path answers `true` (§9.2 Today). Otherwise\n * `sent` says \"Sent to a…@example.com. Check the inbox.\" or \"Could not send.\" — the\n * provider's error stays in the server log (§9.2.7). A 429 says the limiter's line. The\n * button is busy while the request runs; the line is a live region.\n *\n * Not a commit: the test writes nothing, so it is not under the write lock (a demo never\n * reaches the admin area, and the server refuses it anyway). The app adds the catalogue\n * entry `{ id, anchor: MAIL_TEST_ANCHOR, title, keywords }` from these labels.\n */\nexport function MailTestCard({\n mailBackend,\n send,\n address,\n anchor = MAIL_TEST_ANCHOR,\n description,\n labels: labelsProp,\n ...rest\n}: MailTestCardProps) {\n const labels = useKitLabels(\"mailTestCard\", DEFAULT_MAIL_TEST_CARD_LABELS, labelsProp);\n const [busy, setBusy] = useState(false);\n const [outcome, setOutcome] = useState<Outcome | null>(null);\n\n const run = async () => {\n if (busy) return;\n setBusy(true);\n setOutcome(null);\n try {\n const answer = await send();\n const backend = answer.mail_backend;\n if (backend === \"console\") setOutcome({ kind: \"console\", backend });\n else setOutcome(answer.sent ? { kind: \"sent\", backend } : { kind: \"failed\", backend });\n } catch (error) {\n setOutcome(isRateLimited(error) ? { kind: \"limited\", seconds: retryAfterSeconds(error) } : { kind: \"failed\" });\n } finally {\n setBusy(false);\n }\n };\n\n const backend = outcome && \"backend\" in outcome && outcome.backend ? outcome.backend : mailBackend;\n\n return (\n <SettingsSection\n {...rest}\n anchor={anchor}\n title={labels.title}\n description={\n description != null ? (\n <>\n {labels.description} {description}\n </>\n ) : (\n labels.description\n )\n }\n >\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button variant=\"secondary\" pending={busy} onClick={() => void run()}>\n <Send className=\"size-4\" aria-hidden />\n {labels.action}\n </Button>\n {backend && (\n <Chip size=\"sm\" data-slot=\"mail-backend\">\n {labels.transport(backend)}\n </Chip>\n )}\n </div>\n {outcome?.kind === \"sent\" && (\n <AlertBanner variant=\"inline\" block tone=\"success\">\n <span data-private>{labels.sent(maskMailAddress(address))}</span>\n </AlertBanner>\n )}\n {outcome?.kind === \"console\" && (\n <AlertBanner variant=\"inline\" block tone=\"info\">\n {labels.console}\n </AlertBanner>\n )}\n {outcome?.kind === \"failed\" && (\n <AlertBanner variant=\"inline\" block tone=\"danger\">\n {labels.failed}\n </AlertBanner>\n )}\n {outcome?.kind === \"limited\" && (\n <AlertBanner variant=\"inline\" block tone=\"warning\">\n {labels.rateLimited(outcome.seconds)}\n </AlertBanner>\n )}\n </div>\n </SettingsSection>\n );\n}\n"],"mappings":";AA8IU,mBAWE,KAXF;AA9IV,SAAS,gBAAgB;AAEzB,SAAS,YAAY;AAErB,SAAS,oBAAwC;AACjD,SAAS,eAAe,yBAAyB;AACjD,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,uBAAkD;AAC3D,SAAS,qCAAqC;AA0CvC,MAAM,mBAAmB;AAwBzB,SAAS,gBAAgB,SAAyB;AACvD,QAAM,OAAO,QAAQ,KAAK;AAC1B,QAAM,KAAK,KAAK,YAAY,GAAG;AAC/B,MAAI,MAAM,EAAG,QAAO,SAAS,KAAK,KAAK,GAAG,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;AAC7D,SAAO,GAAG,MAAM,KAAK,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,SAAI,KAAK,MAAM,EAAE,CAAC;AAC9D;AAuBO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,GAAsB;AACpB,QAAM,SAAS,aAAa,gBAAgB,+BAA+B,UAAU;AACrF,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAyB,IAAI;AAE3D,QAAM,MAAM,YAAY;AACtB,QAAI,KAAM;AACV,YAAQ,IAAI;AACZ,eAAW,IAAI;AACf,QAAI;AACF,YAAM,SAAS,MAAM,KAAK;AAC1B,YAAMA,WAAU,OAAO;AACvB,UAAIA,aAAY,UAAW,YAAW,EAAE,MAAM,WAAW,SAAAA,SAAQ,CAAC;AAAA,UAC7D,YAAW,OAAO,OAAO,EAAE,MAAM,QAAQ,SAAAA,SAAQ,IAAI,EAAE,MAAM,UAAU,SAAAA,SAAQ,CAAC;AAAA,IACvF,SAAS,OAAO;AACd,iBAAW,cAAc,KAAK,IAAI,EAAE,MAAM,WAAW,SAAS,kBAAkB,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAAA,IAC/G,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,QAAM,UAAU,WAAW,aAAa,WAAW,QAAQ,UAAU,QAAQ,UAAU;AAEvF,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA,OAAO,OAAO;AAAA,MACd,aACE,eAAe,OACb,iCACG;AAAA,eAAO;AAAA,QAAY;AAAA,QAAE;AAAA,SACxB,IAEA,OAAO;AAAA,MAIX,+BAAC,SAAI,WAAU,aACb;AAAA,6BAAC,SAAI,WAAU,qCACb;AAAA,+BAAC,UAAO,SAAQ,aAAY,SAAS,MAAM,SAAS,MAAM,KAAK,IAAI,GACjE;AAAA,gCAAC,QAAK,WAAU,UAAS,eAAW,MAAC;AAAA,YACpC,OAAO;AAAA,aACV;AAAA,UACC,WACC,oBAAC,QAAK,MAAK,MAAK,aAAU,gBACvB,iBAAO,UAAU,OAAO,GAC3B;AAAA,WAEJ;AAAA,QACC,SAAS,SAAS,UACjB,oBAAC,eAAY,SAAQ,UAAS,OAAK,MAAC,MAAK,WACvC,8BAAC,UAAK,gBAAY,MAAE,iBAAO,KAAK,gBAAgB,OAAO,CAAC,GAAE,GAC5D;AAAA,QAED,SAAS,SAAS,aACjB,oBAAC,eAAY,SAAQ,UAAS,OAAK,MAAC,MAAK,QACtC,iBAAO,SACV;AAAA,QAED,SAAS,SAAS,YACjB,oBAAC,eAAY,SAAQ,UAAS,OAAK,MAAC,MAAK,UACtC,iBAAO,QACV;AAAA,QAED,SAAS,SAAS,aACjB,oBAAC,eAAY,SAAQ,UAAS,OAAK,MAAC,MAAK,WACtC,iBAAO,YAAY,QAAQ,OAAO,GACrC;AAAA,SAEJ;AAAA;AAAA,EACF;AAEJ;","names":["backend"]}
1
+ {"version":3,"sources":["../../src/admin/mail-test-card.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Send } from \"lucide-react\";\n\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { isRateLimited, retryAfterSeconds } from \"../auth/auth-errors\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { Chip } from \"../components/chip\";\nimport { SettingsSection, type SettingsSectionProps } from \"../settings/settings-section\";\nimport { DEFAULT_MAIL_TEST_CARD_LABELS } from \"./mail-test-card-labels\";\n\n/* ── Labels ──────────────────────────────────────────────────────────────── */\n\n/** Every string {@link MailTestCard} renders — the `mailTestCard` namespace of\n * `<UiKitProvider labels>`, overridable per instance through `labels`\n * (docs/admin-harmonization.md §9.2.4). */\nexport interface MailTestCardLabels {\n /** The card's title, and the app's catalogue entry's. */\n title: string;\n /** The sentence under it. */\n description: string;\n /** The transport's chip: \"Transport: resend\". `backend` is the server's `mail_backend`. */\n transport: (backend: string) => string;\n /** The button. */\n action: string;\n /** The answer said \"sent\" through a real transport. `address` is masked (\"a…@example.com\"). */\n sent: (address: string) => string;\n /** `mail_backend` is `console`: said whatever `sent` is — a console answers `true`. */\n console: string;\n /** Not sent, or the request failed. The provider's error stays in the server log. */\n failed: string;\n /** HTTP 429 — the app's limiter (server-kit's `MAIL_TEST_LIMIT`, 3 per 10 minutes);\n * `seconds` from `Retry-After` when the response has one. */\n rateLimited: (seconds?: number) => string;\n /** For the app's catalogue entry (`keywords`); the card never reads it. */\n keywords: string;\n}\n\n// Defined in ./mail-test-card-labels, which holds no component code.\nexport { DEFAULT_MAIL_TEST_CARD_LABELS };\n\n/* ── The card ────────────────────────────────────────────────────────────── */\n\n/** The answer of `POST /admin/mail/test` — server-kit 0.15's `MailTestResult`. */\nexport interface MailTestAnswer {\n sent: boolean;\n /** The transport that took it: `console`, `resend`, `smtp`, … */\n mail_backend: string;\n}\n\n/** The card's default DOM id: its catalogue entry's `anchor`, the `?focus=` target. */\nexport const MAIL_TEST_ANCHOR = \"admin-mail-test\";\n\nexport interface MailTestCardProps\n extends Omit<SettingsSectionProps, \"anchor\" | \"title\" | \"description\" | \"children\"> {\n /** The installation's transport, as the admin page has it (`mail_backend`): the chip\n * before a send. After one, the answer's. Optional (0.46): without it the chip shows\n * from the first answer on, and an app needs no query only for the chip. */\n mailBackend?: string;\n /** The app's `POST /admin/mail/test` (kastlan `/platform/mail/test`), no body: it\n * awaits the app's own send and answers `{sent, mail_backend}`. A rejection is read\n * for a 429 (the limiter); anything else is \"could not send\". */\n send: () => Promise<MailTestAnswer>;\n /** The signed-in admin's own address — where the mail goes. Shown masked. */\n address: string;\n /** Default {@link MAIL_TEST_ANCHOR}; the app's catalogue entry names the same. */\n anchor?: string;\n /** The app's sentence after the kit's description. */\n description?: ReactNode;\n labels?: LabelOverride<MailTestCardLabels>;\n}\n\n/**\n * An address with its local part masked to its first character: \"ada@example.com\" →\n * \"a…@example.com\". The domain stays, so the admin sees which mailbox to open.\n */\nexport function maskMailAddress(address: string): string {\n const text = address.trim();\n const at = text.lastIndexOf(\"@\");\n if (at <= 0) return text === \"\" ? \"\" : `${Array.from(text)[0]}…`;\n return `${Array.from(text.slice(0, at))[0]}…${text.slice(at)}`;\n}\n\ntype Outcome =\n | { kind: \"sent\"; backend: string }\n | { kind: \"console\"; backend: string }\n | { kind: \"failed\"; backend?: string }\n | { kind: \"limited\"; seconds?: number };\n\n/**\n * \"Send a test mail to me\" (docs/admin-harmonization.md §9.2): a `SettingsSection` with a\n * sentence, the transport as a chip, the button and the result line. One mail goes to the\n * signed-in admin's own address, never another, through the app's own send.\n *\n * **The result line reads `mail_backend` first**: with `console` it says \"nothing was sent\"\n * whatever `sent` is, since every console path answers `true` (§9.2 Today). Otherwise\n * `sent` says \"Sent to a…@example.com. Check the inbox.\" or \"Could not send.\" — the\n * provider's error stays in the server log (§9.2.7). A 429 says the limiter's line. The\n * button is busy while the request runs; the line is a live region.\n *\n * Not a commit: the test writes nothing, so it is not under the write lock (a demo never\n * reaches the admin area, and the server refuses it anyway). The app adds the catalogue\n * entry `{ id, anchor: MAIL_TEST_ANCHOR, title, keywords }` from these labels.\n */\nexport function MailTestCard({\n mailBackend,\n send,\n address,\n anchor = MAIL_TEST_ANCHOR,\n description,\n labels: labelsProp,\n ...rest\n}: MailTestCardProps) {\n const labels = useKitLabels(\"mailTestCard\", DEFAULT_MAIL_TEST_CARD_LABELS, labelsProp);\n const [busy, setBusy] = useState(false);\n const [outcome, setOutcome] = useState<Outcome | null>(null);\n // The last transport an answer named: a later send with no answer (429, network) keeps it.\n const [answered, setAnswered] = useState<string | undefined>(undefined);\n\n const run = async () => {\n if (busy) return;\n setBusy(true);\n setOutcome(null);\n try {\n const answer = await send();\n const backend = answer.mail_backend;\n if (backend) setAnswered(backend);\n if (backend === \"console\") setOutcome({ kind: \"console\", backend });\n else setOutcome(answer.sent ? { kind: \"sent\", backend } : { kind: \"failed\", backend });\n } catch (error) {\n setOutcome(isRateLimited(error) ? { kind: \"limited\", seconds: retryAfterSeconds(error) } : { kind: \"failed\" });\n } finally {\n setBusy(false);\n }\n };\n\n const backend = answered ?? mailBackend;\n\n return (\n <SettingsSection\n {...rest}\n anchor={anchor}\n title={labels.title}\n description={\n description != null ? (\n <>\n {labels.description} {description}\n </>\n ) : (\n labels.description\n )\n }\n >\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button variant=\"secondary\" pending={busy} onClick={() => void run()}>\n <Send className=\"size-4\" aria-hidden />\n {labels.action}\n </Button>\n {backend && (\n <Chip size=\"sm\" data-slot=\"mail-backend\">\n {labels.transport(backend)}\n </Chip>\n )}\n </div>\n {outcome?.kind === \"sent\" && (\n <AlertBanner variant=\"inline\" block tone=\"success\">\n <span data-private>{labels.sent(maskMailAddress(address))}</span>\n </AlertBanner>\n )}\n {outcome?.kind === \"console\" && (\n <AlertBanner variant=\"inline\" block tone=\"info\">\n {labels.console}\n </AlertBanner>\n )}\n {outcome?.kind === \"failed\" && (\n <AlertBanner variant=\"inline\" block tone=\"danger\">\n {labels.failed}\n </AlertBanner>\n )}\n {outcome?.kind === \"limited\" && (\n <AlertBanner variant=\"inline\" block tone=\"warning\">\n {labels.rateLimited(outcome.seconds)}\n </AlertBanner>\n )}\n </div>\n </SettingsSection>\n );\n}\n"],"mappings":"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","names":["backend"]}
@@ -27,6 +27,16 @@ export interface AppearanceLabels {
27
27
  * toast or the line under the setting (0.32.1, keksdose's 0.32 report).
28
28
  */
29
29
  saveFailed: string;
30
+ /** The date-format setting's name (0.46, `DateFormatSetting`). */
31
+ dateFormat: string;
32
+ /** One line under it: the format is its own choice, apart from the language. */
33
+ dateFormatHelp: string;
34
+ /** Its first option, followed by that language's sample: "Follow language — 01 Aug 2026". */
35
+ dateFormatAuto: string;
36
+ /** `AccountMenuTextSize`'s A− button (0.46, keksdose live #426): one step smaller. */
37
+ textSmaller: string;
38
+ /** Its A+ button: one step larger. */
39
+ textLarger: string;
30
40
  }
31
41
  export declare const DEFAULT_APPEARANCE_LABELS: AppearanceLabels;
32
42
  /** The `appearance` namespace, resolved — English, then the provider, then `labels`. For
@@ -7,7 +7,12 @@ const DEFAULT_APPEARANCE_LABELS = {
7
7
  contrast: "Contrast",
8
8
  contrastHelp: "More contrast darkens quiet text and lines, and thickens focus frames. System follows this device.",
9
9
  contrastModes: { system: "System", standard: "Standard", more: "More" },
10
- saveFailed: "Couldn't save this to your account. It still applies on this device."
10
+ saveFailed: "Couldn't save this to your account. It still applies on this device.",
11
+ dateFormat: "Date format",
12
+ dateFormatHelp: "How dates are written throughout the app. Independent of the interface language, so you can read the app in English and still see German dates.",
13
+ dateFormatAuto: "Follow language",
14
+ textSmaller: "Smaller text",
15
+ textLarger: "Larger text"
11
16
  };
12
17
  function useAppearanceLabels(labels) {
13
18
  return useKitLabels("appearance", DEFAULT_APPEARANCE_LABELS, labels);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/appearance-labels.ts"],"sourcesContent":["import { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport type { ContrastMode } from \"../theme/contrast\";\nimport type { TextSize } from \"../theme/text-size\";\n\n/**\n * The `appearance` namespace (0.32, docs/text-size-harmonization.md §6): the words of the\n * two new settings in the core `appearance` group — `TextSizeSetting` and\n * `ContrastSetting` — and of any quick switch an app builds from the same vocabulary (the\n * showcase's top bar). Like every kit label: prop > `<UiKitProvider labels={{ appearance\n * }}>` > English.\n */\nexport interface AppearanceLabels {\n /** The text-size setting's name. */\n textSize: string;\n /** One line under it: what changes, so \"Large\" is not mistaken for zoom. */\n textSizeHelp: string;\n /** The three steps, in {@link TEXT_SIZES} order. */\n textSizes: Record<TextSize, string>;\n /** The contrast setting's name. */\n contrast: string;\n /** One line under it. */\n contrastHelp: string;\n /** The three choices, in {@link CONTRAST_MODES} order. */\n contrastModes: Record<ContrastMode, string>;\n /**\n * The account could not keep the pick (`useAccountAppearance`'s `pickTextSize` /\n * `pickContrast` rejected): this device keeps it, the account doesn't. For the app's\n * toast or the line under the setting (0.32.1, keksdose's 0.32 report).\n */\n saveFailed: string;\n}\n\nexport const DEFAULT_APPEARANCE_LABELS: AppearanceLabels = {\n textSize: \"Text size\",\n textSizeHelp: \"Larger letters, and a layout that makes room for them.\",\n textSizes: { normal: \"Normal\", large: \"Large\", xlarge: \"Extra large\" },\n contrast: \"Contrast\",\n contrastHelp: \"More contrast darkens quiet text and lines, and thickens focus frames. System follows this device.\",\n contrastModes: { system: \"System\", standard: \"Standard\", more: \"More\" },\n saveFailed: \"Couldn't save this to your account. It still applies on this device.\",\n};\n\n/** The `appearance` namespace, resolved — English, then the provider, then `labels`. For\n * an app's catalogue entries and quick switches, without restating the defaults. */\nexport function useAppearanceLabels(labels?: LabelOverride<AppearanceLabels>): AppearanceLabels {\n return useKitLabels(\"appearance\", DEFAULT_APPEARANCE_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAwC;AAgC1C,MAAM,4BAA8C;AAAA,EACzD,UAAU;AAAA,EACV,cAAc;AAAA,EACd,WAAW,EAAE,QAAQ,UAAU,OAAO,SAAS,QAAQ,cAAc;AAAA,EACrE,UAAU;AAAA,EACV,cAAc;AAAA,EACd,eAAe,EAAE,QAAQ,UAAU,UAAU,YAAY,MAAM,OAAO;AAAA,EACtE,YAAY;AACd;AAIO,SAAS,oBAAoB,QAA4D;AAC9F,SAAO,aAAa,cAAc,2BAA2B,MAAM;AACrE;","names":[]}
1
+ {"version":3,"sources":["../../src/components/appearance-labels.ts"],"sourcesContent":["import { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport type { ContrastMode } from \"../theme/contrast\";\nimport type { TextSize } from \"../theme/text-size\";\n\n/**\n * The `appearance` namespace (0.32, docs/text-size-harmonization.md §6): the words of the\n * two new settings in the core `appearance` group — `TextSizeSetting` and\n * `ContrastSetting` — and of any quick switch an app builds from the same vocabulary (the\n * showcase's top bar). Like every kit label: prop > `<UiKitProvider labels={{ appearance\n * }}>` > English.\n */\nexport interface AppearanceLabels {\n /** The text-size setting's name. */\n textSize: string;\n /** One line under it: what changes, so \"Large\" is not mistaken for zoom. */\n textSizeHelp: string;\n /** The three steps, in {@link TEXT_SIZES} order. */\n textSizes: Record<TextSize, string>;\n /** The contrast setting's name. */\n contrast: string;\n /** One line under it. */\n contrastHelp: string;\n /** The three choices, in {@link CONTRAST_MODES} order. */\n contrastModes: Record<ContrastMode, string>;\n /**\n * The account could not keep the pick (`useAccountAppearance`'s `pickTextSize` /\n * `pickContrast` rejected): this device keeps it, the account doesn't. For the app's\n * toast or the line under the setting (0.32.1, keksdose's 0.32 report).\n */\n saveFailed: string;\n /** The date-format setting's name (0.46, `DateFormatSetting`). */\n dateFormat: string;\n /** One line under it: the format is its own choice, apart from the language. */\n dateFormatHelp: string;\n /** Its first option, followed by that language's sample: \"Follow language — 01 Aug 2026\". */\n dateFormatAuto: string;\n /** `AccountMenuTextSize`'s A− button (0.46, keksdose live #426): one step smaller. */\n textSmaller: string;\n /** Its A+ button: one step larger. */\n textLarger: string;\n}\n\nexport const DEFAULT_APPEARANCE_LABELS: AppearanceLabels = {\n textSize: \"Text size\",\n textSizeHelp: \"Larger letters, and a layout that makes room for them.\",\n textSizes: { normal: \"Normal\", large: \"Large\", xlarge: \"Extra large\" },\n contrast: \"Contrast\",\n contrastHelp: \"More contrast darkens quiet text and lines, and thickens focus frames. System follows this device.\",\n contrastModes: { system: \"System\", standard: \"Standard\", more: \"More\" },\n saveFailed: \"Couldn't save this to your account. It still applies on this device.\",\n dateFormat: \"Date format\",\n dateFormatHelp:\n \"How dates are written throughout the app. Independent of the interface language, so you can read the app in English and still see German dates.\",\n dateFormatAuto: \"Follow language\",\n textSmaller: \"Smaller text\",\n textLarger: \"Larger text\",\n};\n\n/** The `appearance` namespace, resolved — English, then the provider, then `labels`. For\n * an app's catalogue entries and quick switches, without restating the defaults. */\nexport function useAppearanceLabels(labels?: LabelOverride<AppearanceLabels>): AppearanceLabels {\n return useKitLabels(\"appearance\", DEFAULT_APPEARANCE_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAwC;AA0C1C,MAAM,4BAA8C;AAAA,EACzD,UAAU;AAAA,EACV,cAAc;AAAA,EACd,WAAW,EAAE,QAAQ,UAAU,OAAO,SAAS,QAAQ,cAAc;AAAA,EACrE,UAAU;AAAA,EACV,cAAc;AAAA,EACd,eAAe,EAAE,QAAQ,UAAU,UAAU,YAAY,MAAM,OAAO;AAAA,EACtE,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,gBACE;AAAA,EACF,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,YAAY;AACd;AAIO,SAAS,oBAAoB,QAA4D;AAC9F,SAAO,aAAa,cAAc,2BAA2B,MAAM;AACrE;","names":[]}
@@ -4,7 +4,7 @@ import { useRef } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { cn } from "../lib/cn.js";
6
6
  import { Tooltip } from "./tooltip.js";
7
- import { ROW_ACTIONS_KEY } from "./data-table-row-actions.js";
7
+ import { isTableOwnColumn } from "./data-table-row-actions.js";
8
8
  import { columnLabel } from "./data-table-shared.js";
9
9
  function ColumnSettingsPanel({
10
10
  columns,
@@ -78,7 +78,7 @@ function ColumnSettingsPanel({
78
78
  }
79
79
  ),
80
80
  /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: columns.map((col) => {
81
- if (col.key === ROW_ACTIONS_KEY) return null;
81
+ if (isTableOwnColumn(col.key)) return null;
82
82
  const checked = !hiddenCols.has(col.key);
83
83
  const headerLabel = columnLabel(col);
84
84
  return /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-column-settings.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport type { Dispatch, SetStateAction } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Tooltip } from \"./tooltip\";\nimport type { DataTableLabels } from \"./data-table-labels\";\nimport { ROW_ACTIONS_KEY } from \"./data-table-row-actions\";\nimport { columnLabel } from \"./data-table-shared\";\nimport type { DataTableColumn } from \"./data-table\";\n\n// DataTable's column settings on a wide screen: the vertical \"Columns (n/m)\" rail and\n// the drawer it opens, with \"Auto-size\" and a show/hide box per column. Moved out of\n// data-table.tsx whole (refactor audit 2026-10, MOD-3 step 5).\n\n/** The \"Columns\" rail and its drawer (`columnSettings`). */\nexport function ColumnSettingsPanel<T>({\n columns,\n hiddenCols,\n setHiddenCols,\n showSettings,\n setShowSettings,\n columnsCountLabel,\n labels,\n autoSizeAll,\n}: {\n /** Every column, hidden ones included; the actions column is left out here. */\n columns: DataTableColumn<T>[];\n hiddenCols: Set<string>;\n setHiddenCols: Dispatch<SetStateAction<Set<string>>>;\n showSettings: boolean;\n setShowSettings: (open: boolean) => void;\n /** \"Columns (n/m)\", the rail's text and name. */\n columnsCountLabel: string;\n labels: DataTableLabels;\n autoSizeAll: () => void;\n}) {\n // The \"Columns\" rail button — where the drawer's X hands focus back (BUG-7a).\n const columnsRail = useRef<HTMLButtonElement | null>(null);\n return (\n <>\n <Tooltip label={columnsCountLabel} portal>\n <button\n ref={columnsRail}\n type=\"button\"\n onClick={() => setShowSettings(true)}\n aria-label={columnsCountLabel}\n aria-hidden={showSettings}\n tabIndex={showSettings ? -1 : 0}\n className={cn(\n \"group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out\",\n \"border-s border-subtle\",\n \"hover:bg-hover\",\n showSettings ? \"w-0 border-s-0\" : \"w-8 cursor-pointer\",\n )}\n >\n <span\n className=\"whitespace-nowrap text-xs font-medium tracking-wide text-muted group-hover:text-secondary\"\n style={{ writingMode: \"vertical-rl\", transform: \"rotate(180deg)\" }}\n >\n {columnsCountLabel}\n </span>\n </button>\n </Tooltip>\n <div\n // Closed, the drawer is only `w-0`: without `inert` its X, Auto-size and one\n // checkbox per column stayed tab stops and in the accessibility tree, and Tab\n // after the last cell walked into controls nobody could see (BUG-7a).\n inert={!showSettings || undefined}\n className={cn(\n \"shrink-0 overflow-hidden transition-[width] duration-200 ease-out\",\n showSettings ? \"w-56\" : \"w-0\",\n )}\n >\n <div className=\"flex w-56 flex-col border-s border-subtle p-2\">\n <div className=\"mb-2 flex items-center justify-between gap-1\">\n <div className=\"text-xs font-medium uppercase tracking-wide text-muted\">\n {columnsCountLabel}\n </div>\n <Tooltip label={labels.close} portal>\n <button\n type=\"button\"\n onClick={() => {\n setShowSettings(false);\n // Back to the rail that opened it: the X is about to go inert, and\n // focus would fall to <body> with it (BUG-7a).\n columnsRail.current?.focus();\n }}\n aria-label={labels.close}\n className=\"rounded p-0.5 text-placeholder hover:bg-hover hover:text-secondary\"\n >\n <X className=\"size-4\" />\n </button>\n </Tooltip>\n </div>\n <button\n type=\"button\"\n onClick={autoSizeAll}\n className=\"mb-2 rounded border border-subtle px-2 py-1 text-xs font-medium text-secondary hover:bg-hover\"\n >\n {labels.autoSize}\n </button>\n <div className=\"flex flex-col gap-0.5\">\n {columns.map((col) => {\n if (col.key === ROW_ACTIONS_KEY) return null;\n const checked = !hiddenCols.has(col.key);\n const headerLabel = columnLabel(col);\n return (\n <label\n key={col.key}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-hover\"\n >\n <input\n type=\"checkbox\"\n className=\"size-3.5 shrink-0\"\n checked={checked}\n onChange={(e) => {\n setHiddenCols((s) => {\n const next = new Set(s);\n if (e.target.checked) next.delete(col.key);\n else next.add(col.key);\n return next;\n });\n }}\n />\n {/* The panel is `w-56`: a long column name wraps from Large up rather\n than reading \"Accoun…\" beside its box (text size §10.17, round\n 0.34 T1). */}\n <span className=\"min-w-0 truncate-until-large\">{headerLabel}</span>\n </label>\n );\n })}\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":";AAuCI,mBAgBM,KAmBA,YAnCN;AAvCJ,SAAS,cAAc;AAEvB,SAAS,SAAS;AAClB,SAAS,UAAU;AACnB,SAAS,eAAe;AAExB,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAQrB,SAAS,oBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAWG;AAED,QAAM,cAAc,OAAiC,IAAI;AACzD,SACE,iCACE;AAAA,wBAAC,WAAQ,OAAO,mBAAmB,QAAM,MACvC;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAS,MAAM,gBAAgB,IAAI;AAAA,QACnC,cAAY;AAAA,QACZ,eAAa;AAAA,QACb,UAAU,eAAe,KAAK;AAAA,QAC9B,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,mBAAmB;AAAA,QACpC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,aAAa,eAAe,WAAW,iBAAiB;AAAA,YAEhE;AAAA;AAAA,QACH;AAAA;AAAA,IACF,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QAIC,OAAO,CAAC,gBAAgB;AAAA,QACxB,WAAW;AAAA,UACT;AAAA,UACA,eAAe,SAAS;AAAA,QAC1B;AAAA,QAEA,+BAAC,SAAI,WAAU,iDACb;AAAA,+BAAC,SAAI,WAAU,gDACb;AAAA,gCAAC,SAAI,WAAU,0DACZ,6BACH;AAAA,YACA,oBAAC,WAAQ,OAAO,OAAO,OAAO,QAAM,MAClC;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,MAAM;AACb,kCAAgB,KAAK;AAGrB,8BAAY,SAAS,MAAM;AAAA,gBAC7B;AAAA,gBACA,cAAY,OAAO;AAAA,gBACnB,WAAU;AAAA,gBAEV,8BAAC,KAAE,WAAU,UAAS;AAAA;AAAA,YACxB,GACF;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAU;AAAA,cAET,iBAAO;AAAA;AAAA,UACV;AAAA,UACA,oBAAC,SAAI,WAAU,yBACZ,kBAAQ,IAAI,CAAC,QAAQ;AACpB,gBAAI,IAAI,QAAQ,gBAAiB,QAAO;AACxC,kBAAM,UAAU,CAAC,WAAW,IAAI,IAAI,GAAG;AACvC,kBAAM,cAAc,YAAY,GAAG;AACnC,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAU;AAAA,gBAEV;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV;AAAA,sBACA,UAAU,CAAC,MAAM;AACf,sCAAc,CAAC,MAAM;AACnB,gCAAM,OAAO,IAAI,IAAI,CAAC;AACtB,8BAAI,EAAE,OAAO,QAAS,MAAK,OAAO,IAAI,GAAG;AAAA,8BACpC,MAAK,IAAI,IAAI,GAAG;AACrB,iCAAO;AAAA,wBACT,CAAC;AAAA,sBACH;AAAA;AAAA,kBACF;AAAA,kBAIA,oBAAC,UAAK,WAAU,gCAAgC,uBAAY;AAAA;AAAA;AAAA,cAnBvD,IAAI;AAAA,YAoBX;AAAA,UAEJ,CAAC,GACH;AAAA,WACF;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/data-table-column-settings.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport type { Dispatch, SetStateAction } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Tooltip } from \"./tooltip\";\nimport type { DataTableLabels } from \"./data-table-labels\";\nimport { isTableOwnColumn } from \"./data-table-row-actions\";\nimport { columnLabel } from \"./data-table-shared\";\nimport type { DataTableColumn } from \"./data-table\";\n\n// DataTable's column settings on a wide screen: the vertical \"Columns (n/m)\" rail and\n// the drawer it opens, with \"Auto-size\" and a show/hide box per column. Moved out of\n// data-table.tsx whole (refactor audit 2026-10, MOD-3 step 5).\n\n/** The \"Columns\" rail and its drawer (`columnSettings`). */\nexport function ColumnSettingsPanel<T>({\n columns,\n hiddenCols,\n setHiddenCols,\n showSettings,\n setShowSettings,\n columnsCountLabel,\n labels,\n autoSizeAll,\n}: {\n /** Every column, hidden ones included; the actions column is left out here. */\n columns: DataTableColumn<T>[];\n hiddenCols: Set<string>;\n setHiddenCols: Dispatch<SetStateAction<Set<string>>>;\n showSettings: boolean;\n setShowSettings: (open: boolean) => void;\n /** \"Columns (n/m)\", the rail's text and name. */\n columnsCountLabel: string;\n labels: DataTableLabels;\n autoSizeAll: () => void;\n}) {\n // The \"Columns\" rail button — where the drawer's X hands focus back (BUG-7a).\n const columnsRail = useRef<HTMLButtonElement | null>(null);\n return (\n <>\n <Tooltip label={columnsCountLabel} portal>\n <button\n ref={columnsRail}\n type=\"button\"\n onClick={() => setShowSettings(true)}\n aria-label={columnsCountLabel}\n aria-hidden={showSettings}\n tabIndex={showSettings ? -1 : 0}\n className={cn(\n \"group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out\",\n \"border-s border-subtle\",\n \"hover:bg-hover\",\n showSettings ? \"w-0 border-s-0\" : \"w-8 cursor-pointer\",\n )}\n >\n <span\n className=\"whitespace-nowrap text-xs font-medium tracking-wide text-muted group-hover:text-secondary\"\n style={{ writingMode: \"vertical-rl\", transform: \"rotate(180deg)\" }}\n >\n {columnsCountLabel}\n </span>\n </button>\n </Tooltip>\n <div\n // Closed, the drawer is only `w-0`: without `inert` its X, Auto-size and one\n // checkbox per column stayed tab stops and in the accessibility tree, and Tab\n // after the last cell walked into controls nobody could see (BUG-7a).\n inert={!showSettings || undefined}\n className={cn(\n \"shrink-0 overflow-hidden transition-[width] duration-200 ease-out\",\n showSettings ? \"w-56\" : \"w-0\",\n )}\n >\n <div className=\"flex w-56 flex-col border-s border-subtle p-2\">\n <div className=\"mb-2 flex items-center justify-between gap-1\">\n <div className=\"text-xs font-medium uppercase tracking-wide text-muted\">\n {columnsCountLabel}\n </div>\n <Tooltip label={labels.close} portal>\n <button\n type=\"button\"\n onClick={() => {\n setShowSettings(false);\n // Back to the rail that opened it: the X is about to go inert, and\n // focus would fall to <body> with it (BUG-7a).\n columnsRail.current?.focus();\n }}\n aria-label={labels.close}\n className=\"rounded p-0.5 text-placeholder hover:bg-hover hover:text-secondary\"\n >\n <X className=\"size-4\" />\n </button>\n </Tooltip>\n </div>\n <button\n type=\"button\"\n onClick={autoSizeAll}\n className=\"mb-2 rounded border border-subtle px-2 py-1 text-xs font-medium text-secondary hover:bg-hover\"\n >\n {labels.autoSize}\n </button>\n <div className=\"flex flex-col gap-0.5\">\n {columns.map((col) => {\n if (isTableOwnColumn(col.key)) return null;\n const checked = !hiddenCols.has(col.key);\n const headerLabel = columnLabel(col);\n return (\n <label\n key={col.key}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-hover\"\n >\n <input\n type=\"checkbox\"\n className=\"size-3.5 shrink-0\"\n checked={checked}\n onChange={(e) => {\n setHiddenCols((s) => {\n const next = new Set(s);\n if (e.target.checked) next.delete(col.key);\n else next.add(col.key);\n return next;\n });\n }}\n />\n {/* The panel is `w-56`: a long column name wraps from Large up rather\n than reading \"Accoun…\" beside its box (text size §10.17, round\n 0.34 T1). */}\n <span className=\"min-w-0 truncate-until-large\">{headerLabel}</span>\n </label>\n );\n })}\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":";AAuCI,mBAgBM,KAmBA,YAnCN;AAvCJ,SAAS,cAAc;AAEvB,SAAS,SAAS;AAClB,SAAS,UAAU;AACnB,SAAS,eAAe;AAExB,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAQrB,SAAS,oBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAWG;AAED,QAAM,cAAc,OAAiC,IAAI;AACzD,SACE,iCACE;AAAA,wBAAC,WAAQ,OAAO,mBAAmB,QAAM,MACvC;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,SAAS,MAAM,gBAAgB,IAAI;AAAA,QACnC,cAAY;AAAA,QACZ,eAAa;AAAA,QACb,UAAU,eAAe,KAAK;AAAA,QAC9B,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,mBAAmB;AAAA,QACpC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,aAAa,eAAe,WAAW,iBAAiB;AAAA,YAEhE;AAAA;AAAA,QACH;AAAA;AAAA,IACF,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QAIC,OAAO,CAAC,gBAAgB;AAAA,QACxB,WAAW;AAAA,UACT;AAAA,UACA,eAAe,SAAS;AAAA,QAC1B;AAAA,QAEA,+BAAC,SAAI,WAAU,iDACb;AAAA,+BAAC,SAAI,WAAU,gDACb;AAAA,gCAAC,SAAI,WAAU,0DACZ,6BACH;AAAA,YACA,oBAAC,WAAQ,OAAO,OAAO,OAAO,QAAM,MAClC;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAS,MAAM;AACb,kCAAgB,KAAK;AAGrB,8BAAY,SAAS,MAAM;AAAA,gBAC7B;AAAA,gBACA,cAAY,OAAO;AAAA,gBACnB,WAAU;AAAA,gBAEV,8BAAC,KAAE,WAAU,UAAS;AAAA;AAAA,YACxB,GACF;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS;AAAA,cACT,WAAU;AAAA,cAET,iBAAO;AAAA;AAAA,UACV;AAAA,UACA,oBAAC,SAAI,WAAU,yBACZ,kBAAQ,IAAI,CAAC,QAAQ;AACpB,gBAAI,iBAAiB,IAAI,GAAG,EAAG,QAAO;AACtC,kBAAM,UAAU,CAAC,WAAW,IAAI,IAAI,GAAG;AACvC,kBAAM,cAAc,YAAY,GAAG;AACnC,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAU;AAAA,gBAEV;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV;AAAA,sBACA,UAAU,CAAC,MAAM;AACf,sCAAc,CAAC,MAAM;AACnB,gCAAM,OAAO,IAAI,IAAI,CAAC;AACtB,8BAAI,EAAE,OAAO,QAAS,MAAK,OAAO,IAAI,GAAG;AAAA,8BACpC,MAAK,IAAI,IAAI,GAAG;AACrB,iCAAO;AAAA,wBACT,CAAC;AAAA,sBACH;AAAA;AAAA,kBACF;AAAA,kBAIA,oBAAC,UAAK,WAAU,gCAAgC,uBAAY;AAAA;AAAA;AAAA,cAnBvD,IAAI;AAAA,YAoBX;AAAA,UAEJ,CAAC,GACH;AAAA,WACF;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
@@ -7,7 +7,7 @@ import { defaultFilterState, isFilterActive, resolveFilter } from "./data-table-
7
7
  import { FilterPopover } from "./data-table-filter-popover.js";
8
8
  import { Popover } from "./popover.js";
9
9
  import { Tooltip } from "./tooltip.js";
10
- import { ROW_ACTIONS_KEY } from "./data-table-row-actions.js";
10
+ import { isTableOwnColumn } from "./data-table-row-actions.js";
11
11
  import { columnLabel, logicalAlign, splitCellProps } from "./data-table-shared.js";
12
12
  const END_ALIGN = /(^|\s)text-end(?=\s|$)/;
13
13
  function DataTableHead({
@@ -157,7 +157,7 @@ function DataTableHead({
157
157
  ]
158
158
  }
159
159
  ),
160
- resizable && col.key !== ROW_ACTIONS_KEY && /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- a
160
+ resizable && !isTableOwnColumn(col.key) && /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- a
161
161
  pointer-only drag handle for a visual preference (column width); it is
162
162
  deliberately not a tab stop, and no content or action depends on it. */
163
163
  /* @__PURE__ */ jsx(