@eifi1/ui-kit 0.46.0 → 0.47.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 (148) hide show
  1. package/README.md +12 -11
  2. package/dist/admin/user-roster.d.ts +6 -2
  3. package/dist/admin/user-roster.js +7 -3
  4. package/dist/admin/user-roster.js.map +1 -1
  5. package/dist/components/data-table-row-actions.d.ts +22 -5
  6. package/dist/components/data-table-row-actions.js +14 -5
  7. package/dist/components/data-table-row-actions.js.map +1 -1
  8. package/dist/components/data-table.d.ts +16 -1
  9. package/dist/components/data-table.js +20 -1
  10. package/dist/components/data-table.js.map +1 -1
  11. package/dist/components/row-actions.d.ts +37 -5
  12. package/dist/components/row-actions.js +22 -5
  13. package/dist/components/row-actions.js.map +1 -1
  14. package/dist/components/tabs.d.ts +24 -16
  15. package/dist/components/tabs.js +37 -31
  16. package/dist/components/tabs.js.map +1 -1
  17. package/dist/components/translation-review.js +3 -1
  18. package/dist/components/translation-review.js.map +1 -1
  19. package/dist/components/ui-labels.js +1 -1
  20. package/dist/components/ui-labels.js.map +1 -1
  21. package/dist/data-table.d.ts +1 -0
  22. package/dist/data-table.js +2 -0
  23. package/dist/data-table.js.map +1 -1
  24. package/dist/eslint/index.js +1 -1
  25. package/dist/feedback/feedback-attachment-download.d.ts +19 -0
  26. package/dist/feedback/feedback-attachment-download.js +59 -0
  27. package/dist/feedback/feedback-attachment-download.js.map +1 -0
  28. package/dist/feedback/feedback-comment-thread.d.ts +17 -6
  29. package/dist/feedback/feedback-comment-thread.js +21 -2
  30. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  31. package/dist/feedback/feedback-entry-file.d.ts +61 -0
  32. package/dist/feedback/feedback-entry-file.js +76 -0
  33. package/dist/feedback/feedback-entry-file.js.map +1 -0
  34. package/dist/feedback/feedback-public-list.d.ts +18 -5
  35. package/dist/feedback/feedback-public-list.js +8 -0
  36. package/dist/feedback/feedback-public-list.js.map +1 -1
  37. package/dist/feedback/feedback-public-types.d.ts +7 -2
  38. package/dist/feedback/feedback-replies.d.ts +26 -9
  39. package/dist/feedback/feedback-replies.js +20 -10
  40. package/dist/feedback/feedback-replies.js.map +1 -1
  41. package/dist/feedback/feedback-row-detail-labels.js +0 -1
  42. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  43. package/dist/feedback/feedback-row-detail.d.ts +23 -10
  44. package/dist/feedback/feedback-row-detail.js +10 -54
  45. package/dist/feedback/feedback-row-detail.js.map +1 -1
  46. package/dist/feedback.d.ts +1 -0
  47. package/dist/feedback.js +1 -0
  48. package/dist/feedback.js.map +1 -1
  49. package/dist/i18n/german.js +1 -6
  50. package/dist/i18n/german.js.map +1 -1
  51. package/dist/i18n/locales/en.js +1 -6
  52. package/dist/i18n/locales/en.js.map +2 -2
  53. package/dist/i18n/locales/es.js +1 -6
  54. package/dist/i18n/locales/es.js.map +1 -1
  55. package/dist/i18n/locales/fr.js +1 -6
  56. package/dist/i18n/locales/fr.js.map +1 -1
  57. package/dist/i18n/locales/hu.js +1 -6
  58. package/dist/i18n/locales/hu.js.map +1 -1
  59. package/dist/i18n/locales/it.js +1 -6
  60. package/dist/i18n/locales/it.js.map +1 -1
  61. package/dist/i18n/locales/zh.js +1 -6
  62. package/dist/i18n/locales/zh.js.map +1 -1
  63. package/dist/i18n/review.js +0 -1
  64. package/dist/i18n/review.js.map +1 -1
  65. package/dist/index.d.ts +5 -7
  66. package/dist/index.js +14 -5
  67. package/dist/index.js.map +1 -1
  68. package/dist/notifications/device-zone-report.d.ts +7 -0
  69. package/dist/notifications/device-zone-report.js +8 -1
  70. package/dist/notifications/device-zone-report.js.map +1 -1
  71. package/dist/notifications/feedback-notification-types.d.ts +14 -1
  72. package/dist/notifications/feedback-notification-types.js +5 -1
  73. package/dist/notifications/feedback-notification-types.js.map +1 -1
  74. package/dist/notifications/notification-bell.d.ts +1 -1
  75. package/dist/notifications/notification-bell.js.map +1 -1
  76. package/dist/notifications/notification-inbox.d.ts +12 -3
  77. package/dist/notifications/notification-inbox.js +2 -2
  78. package/dist/notifications/notification-inbox.js.map +1 -1
  79. package/dist/notifications/notification-types.d.ts +0 -3
  80. package/dist/notifications/notifications-labels.d.ts +2 -13
  81. package/dist/notifications/notifications-labels.js +0 -4
  82. package/dist/notifications/notifications-labels.js.map +1 -1
  83. package/dist/notifications/quiet-hours-setting.d.ts +3 -8
  84. package/dist/notifications/quiet-hours-setting.js +8 -3
  85. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  86. package/dist/offline/local-first.d.ts +9 -0
  87. package/dist/offline/local-first.js +14 -0
  88. package/dist/offline/local-first.js.map +1 -1
  89. package/dist/offline.d.ts +1 -1
  90. package/dist/offline.js +2 -1
  91. package/dist/offline.js.map +1 -1
  92. package/dist/sync/db.d.ts +17 -0
  93. package/dist/sync/db.js +10 -1
  94. package/dist/sync/db.js.map +1 -1
  95. package/dist/sync/engine.d.ts +11 -9
  96. package/dist/sync/engine.js.map +1 -1
  97. package/dist/sync/hooks.d.ts +5 -0
  98. package/dist/sync/hooks.js.map +1 -1
  99. package/dist/sync.d.ts +2 -2
  100. package/dist/sync.js +5 -2
  101. package/dist/sync.js.map +1 -1
  102. package/dist/theme/theme-store.d.ts +4 -4
  103. package/dist/theme/theme-store.js +1 -0
  104. package/dist/theme/theme-store.js.map +1 -1
  105. package/package.json +2 -2
  106. package/src/admin/user-roster.tsx +12 -3
  107. package/src/components/data-table-row-actions.tsx +33 -9
  108. package/src/components/data-table.tsx +46 -3
  109. package/src/components/row-actions.tsx +73 -10
  110. package/src/components/tabs.tsx +66 -44
  111. package/src/components/translation-review.tsx +3 -1
  112. package/src/components/ui-labels.ts +1 -1
  113. package/src/data-table.ts +3 -0
  114. package/src/feedback/feedback-attachment-download.tsx +77 -0
  115. package/src/feedback/feedback-comment-thread.tsx +42 -7
  116. package/src/feedback/feedback-entry-file.tsx +150 -0
  117. package/src/feedback/feedback-public-list.tsx +25 -4
  118. package/src/feedback/feedback-public-types.ts +7 -2
  119. package/src/feedback/feedback-replies.tsx +46 -19
  120. package/src/feedback/feedback-row-detail-labels.ts +0 -1
  121. package/src/feedback/feedback-row-detail.tsx +35 -86
  122. package/src/feedback.ts +1 -0
  123. package/src/i18n/german.ts +1 -6
  124. package/src/i18n/locales/es.ts +1 -6
  125. package/src/i18n/locales/fr.ts +1 -6
  126. package/src/i18n/locales/hu.ts +1 -6
  127. package/src/i18n/locales/it.ts +1 -6
  128. package/src/i18n/locales/zh.ts +1 -6
  129. package/src/i18n/review.ts +0 -1
  130. package/src/index.ts +19 -5
  131. package/src/notifications/device-zone-report.ts +14 -1
  132. package/src/notifications/feedback-notification-types.ts +19 -1
  133. package/src/notifications/notification-bell.tsx +1 -1
  134. package/src/notifications/notification-inbox.tsx +15 -5
  135. package/src/notifications/notification-types.ts +0 -3
  136. package/src/notifications/notifications-labels.ts +2 -17
  137. package/src/notifications/quiet-hours-setting.tsx +16 -11
  138. package/src/offline/local-first.ts +24 -0
  139. package/src/offline.ts +1 -1
  140. package/src/sync/db.ts +22 -0
  141. package/src/sync/engine.ts +11 -9
  142. package/src/sync/hooks.ts +5 -0
  143. package/src/sync.ts +2 -2
  144. package/src/theme/theme-store.ts +13 -5
  145. package/dist/notifications/time-zone-select.d.ts +0 -53
  146. package/dist/notifications/time-zone-select.js +0 -76
  147. package/dist/notifications/time-zone-select.js.map +0 -1
  148. package/src/notifications/time-zone-select.tsx +0 -129
@@ -202,7 +202,6 @@ export function germanLabels(numberLocale: string): UiKitLabels {
202
202
  outcomeUpdate: "Aktualisieren",
203
203
  outcomePlaceholder: "Was wurde umgesetzt, entschieden oder warum nicht.",
204
204
  rework: "Nacharbeit",
205
- openPage: "Seite öffnen",
206
205
  reworkTitle: "Zur Nacharbeit senden",
207
206
  reworkSend: "Nacharbeit senden",
208
207
  reworkPlaceholder: "Was muss noch angepasst werden? Neue Anforderungen oder Richtungswechsel.",
@@ -541,7 +540,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
541
540
  add: "Tab hinzufügen",
542
541
  remove: (tab) => `${tab} entfernen`,
543
542
  more: "Weitere Tabs",
544
- backToFirst: "Zurück zum ersten Tab",
543
+ previous: "Vorherige Tabs",
545
544
  },
546
545
  appShell: {
547
546
  collapse: "Seitenleiste einklappen",
@@ -1184,7 +1183,6 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1184
1183
  notifications: {
1185
1184
  typesTitle: "Benachrichtigungen zu",
1186
1185
  typesDescription: "Wählen Sie, worüber Sie benachrichtigt werden, und wie.",
1187
- byEmail: "Per E-Mail",
1188
1186
  channelApp: "In der App",
1189
1187
  channelPush: "Push",
1190
1188
  channelEmail: "E-Mail",
@@ -1207,9 +1205,6 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1207
1205
  quietFrom: "Von",
1208
1206
  quietUntil: "Bis",
1209
1207
  quietIncomplete: "Legen Sie eine Start- und eine Endzeit fest. Ruhezeiten gelten erst, wenn beide gesetzt sind.",
1210
- timeZone: "Zeitzone",
1211
- deviceZone: (zone) => `Dieses Gerät ist in der Zeitzone ${zone}.`,
1212
- useDeviceZone: "Übernehmen",
1213
1208
  regions: {
1214
1209
  Africa: "Afrika",
1215
1210
  America: "Amerika",
@@ -193,7 +193,6 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
193
193
  outcomeUpdate: "Actualizar",
194
194
  outcomePlaceholder: "Qué se ha hecho o decidido, o por qué no se abordará.",
195
195
  rework: "Corrección",
196
- openPage: "Abrir página",
197
196
  reworkTitle: "Devolver para corrección",
198
197
  reworkSend: "Solicitar corrección",
199
198
  reworkPlaceholder: "¿Qué falta por pulir? Nuevos requisitos o cambios de rumbo.",
@@ -528,7 +527,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
528
527
  add: "Añadir pestaña",
529
528
  remove: (tab) => `Quitar ${tab}`,
530
529
  more: "Más pestañas",
531
- backToFirst: "Volver a la primera pestaña",
530
+ previous: "Pestañas anteriores",
532
531
  },
533
532
  appShell: {
534
533
  collapse: "Contraer barra lateral",
@@ -1158,7 +1157,6 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1158
1157
  notifications: {
1159
1158
  typesTitle: "Avisarme de",
1160
1159
  typesDescription: "Elija de qué se entera y cómo.",
1161
- byEmail: "Por correo electrónico",
1162
1160
  channelApp: "En la app",
1163
1161
  channelPush: "Push",
1164
1162
  channelEmail: "Correo",
@@ -1182,9 +1180,6 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1182
1180
  quietUntil: "Hasta",
1183
1181
  quietIncomplete:
1184
1182
  "Indique una hora de inicio y una de fin. Las horas de silencio se aplican cuando ambas están definidas.",
1185
- timeZone: "Zona horaria",
1186
- deviceZone: (zone) => `Este dispositivo está en ${zone}.`,
1187
- useDeviceZone: "Usarla",
1188
1183
  regions: {
1189
1184
  Africa: "África",
1190
1185
  America: "América",
@@ -191,7 +191,6 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
191
191
  outcomeUpdate: "Mettre à jour",
192
192
  outcomePlaceholder: "Ce qui a été fait ou décidé, ou pourquoi ce point ne sera pas traité.",
193
193
  rework: "Reprise",
194
- openPage: "Ouvrir la page",
195
194
  reworkTitle: "Renvoyer pour reprise",
196
195
  reworkSend: "Demander la reprise",
197
196
  reworkPlaceholder:
@@ -536,7 +535,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
536
535
  add: "Ajouter un onglet",
537
536
  remove: (tab) => `Supprimer ${tab}`,
538
537
  more: "Autres onglets",
539
- backToFirst: "Retour au premier onglet",
538
+ previous: "Onglets précédents",
540
539
  },
541
540
  appShell: {
542
541
  collapse: "Réduire la barre latérale",
@@ -1186,7 +1185,6 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1186
1185
  notifications: {
1187
1186
  typesTitle: "Me notifier pour",
1188
1187
  typesDescription: "Choisissez ce dont vous êtes averti, et comment.",
1189
- byEmail: "Par e-mail",
1190
1188
  channelApp: "Dans l’app",
1191
1189
  channelPush: "Push",
1192
1190
  channelEmail: "E-mail",
@@ -1210,9 +1208,6 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1210
1208
  quietUntil: "Jusqu’à",
1211
1209
  quietIncomplete:
1212
1210
  "Définissez une heure de début et une heure de fin. Les heures calmes s’appliquent une fois les deux renseignées.",
1213
- timeZone: "Fuseau horaire",
1214
- deviceZone: (zone) => `Cet appareil est dans le fuseau ${zone}.`,
1215
- useDeviceZone: "L’utiliser",
1216
1211
  regions: {
1217
1212
  Africa: "Afrique",
1218
1213
  America: "Amériques",
@@ -190,7 +190,6 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
190
190
  outcomeUpdate: "Frissítés",
191
191
  outcomePlaceholder: "Mi készült el, mi a döntés, vagy miért nem valósul meg.",
192
192
  rework: "Átdolgozás",
193
- openPage: "Oldal megnyitása",
194
193
  reworkTitle: "Visszaküldés átdolgozásra",
195
194
  reworkSend: "Átdolgozás kérése",
196
195
  reworkPlaceholder: "Min kell még finomítani? Új feltételek vagy irányváltás.",
@@ -526,7 +525,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
526
525
  add: "Lap hozzáadása",
527
526
  remove: (tab) => `${tab} eltávolítása`,
528
527
  more: "További lapok",
529
- backToFirst: "Vissza az első lapra",
528
+ previous: "Előző lapok",
530
529
  },
531
530
  appShell: {
532
531
  collapse: "Oldalsáv összecsukása",
@@ -1169,7 +1168,6 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1169
1168
  notifications: {
1170
1169
  typesTitle: "Értesítést kérek ezekről",
1171
1170
  typesDescription: "Válassza ki, miről és hogyan kapjon értesítést.",
1172
- byEmail: "E-mailben",
1173
1171
  channelApp: "Az appban",
1174
1172
  channelPush: "Push",
1175
1173
  channelEmail: "E-mail",
@@ -1193,9 +1191,6 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1193
1191
  quietUntil: "Eddig",
1194
1192
  quietIncomplete:
1195
1193
  "Adjon meg kezdő és záró időpontot is. A csendes órák csak akkor lépnek életbe, ha mindkettő be van állítva.",
1196
- timeZone: "Időzóna",
1197
- deviceZone: (zone) => `Ez az eszköz ebben az időzónában van: ${zone}.`,
1198
- useDeviceZone: "Ezt használom",
1199
1194
  regions: {
1200
1195
  Africa: "Afrika",
1201
1196
  America: "Amerika",
@@ -193,7 +193,6 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
193
193
  outcomeUpdate: "Aggiorna",
194
194
  outcomePlaceholder: "Cosa è stato fatto o deciso, oppure perché non verrà affrontato.",
195
195
  rework: "Rielaborazione",
196
- openPage: "Apri pagina",
197
196
  reworkTitle: "Rimanda in rielaborazione",
198
197
  reworkSend: "Richiedi rielaborazione",
199
198
  reworkPlaceholder: "Cosa va ancora perfezionato? Eventuali nuovi vincoli o cambi di direzione.",
@@ -531,7 +530,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
531
530
  add: "Aggiungi scheda",
532
531
  remove: (tab) => `Rimuovi ${tab}`,
533
532
  more: "Altre schede",
534
- backToFirst: "Torna alla prima scheda",
533
+ previous: "Schede precedenti",
535
534
  },
536
535
  appShell: {
537
536
  collapse: "Comprimi barra laterale",
@@ -1170,7 +1169,6 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1170
1169
  notifications: {
1171
1170
  typesTitle: "Avvisami per",
1172
1171
  typesDescription: "Scelga di cosa essere avvisato, e come.",
1173
- byEmail: "Per e-mail",
1174
1172
  channelApp: "Nell’app",
1175
1173
  channelPush: "Push",
1176
1174
  channelEmail: "E-mail",
@@ -1194,9 +1192,6 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1194
1192
  quietUntil: "Alle",
1195
1193
  quietIncomplete:
1196
1194
  "Imposti sia l’ora di inizio sia quella di fine. Le ore di silenzio valgono solo quando sono impostate entrambe.",
1197
- timeZone: "Fuso orario",
1198
- deviceZone: (zone) => `Questo dispositivo è nel fuso ${zone}.`,
1199
- useDeviceZone: "Usa questo",
1200
1195
  regions: {
1201
1196
  Africa: "Africa",
1202
1197
  America: "Americhe",
@@ -183,7 +183,6 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
183
183
  outcomeUpdate: "更新",
184
184
  outcomePlaceholder: "做了什么、作出了什么决定,或为什么不予处理。",
185
185
  rework: "返工",
186
- openPage: "打开页面",
187
186
  reworkTitle: "退回返工",
188
187
  reworkSend: "提交返工",
189
188
  reworkPlaceholder: "还有哪些需要完善?如有新的限制或方向调整,请一并说明。",
@@ -514,7 +513,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
514
513
  // Quoted, so the spacing rule holds whether the tab's name is Han or Latin.
515
514
  remove: (tab) => `移除“${tab}”`,
516
515
  more: "更多标签页",
517
- backToFirst: "返回第一个标签页",
516
+ previous: "之前的标签页",
518
517
  },
519
518
  appShell: {
520
519
  collapse: "收起侧边栏",
@@ -1129,7 +1128,6 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1129
1128
  notifications: {
1130
1129
  typesTitle: "通知我以下内容",
1131
1130
  typesDescription: "选择要接收哪些通知,以及接收方式。",
1132
- byEmail: "通过电子邮件",
1133
1131
  channelApp: "应用内",
1134
1132
  channelPush: "推送",
1135
1133
  channelEmail: "电子邮件",
@@ -1152,9 +1150,6 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1152
1150
  quietFrom: "从",
1153
1151
  quietUntil: "到",
1154
1152
  quietIncomplete: "请同时设置开始和结束时间。两者都设置后,免打扰时段才会生效。",
1155
- timeZone: "时区",
1156
- deviceZone: (zone) => `此设备位于 ${zone} 时区。`,
1157
- useDeviceZone: "使用此时区",
1158
1153
  regions: {
1159
1154
  Africa: "非洲",
1160
1155
  America: "美洲",
@@ -333,7 +333,6 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
333
333
  "notifications.quietDescription": [[false], [true], [false, true], [true, true]],
334
334
  "notifications.quietZone": [["{{zone}}"]],
335
335
  "notifications.bellUnread": one,
336
- "notifications.deviceZone": [["{{zone}}"]],
337
336
  "notifications.iosInstall": [["{{app}}"]],
338
337
  "notifications.testSent": one,
339
338
  "notifications.explainerBody": [["{{app}}"]],
package/src/index.ts CHANGED
@@ -567,7 +567,15 @@ export {
567
567
  export type { DateFormatPreference, DateFormatOptions, ResolvedDateFormat } from "./lib/date-format";
568
568
  export { DateFormatSetting } from "./components/date-format-setting";
569
569
  export type { DateFormatSettingProps } from "./components/date-format-setting";
570
- export { RowActions, rowActionsColumn, DEFAULT_ROW_ACTIONS_LABELS } from "./components/row-actions";
570
+ export {
571
+ RowActions,
572
+ rowActionsColumn,
573
+ DEFAULT_ROW_ACTIONS_LABELS,
574
+ // 0.47.0 (keksdose N17): a hand-built table's actions heading and cell.
575
+ RowActionsHead,
576
+ ROW_ACTIONS_CELL_CLASS,
577
+ ROW_ACTIONS_HEAD_CLASS,
578
+ } from "./components/row-actions";
571
579
  export type {
572
580
  RowAction,
573
581
  RowActionList,
@@ -576,6 +584,7 @@ export type {
576
584
  RowActionsCollapse,
577
585
  RowActionsSize,
578
586
  RowActionsColumnOptions,
587
+ RowActionsHeadProps,
579
588
  RowActionTone,
580
589
  } from "./components/row-actions";
581
590
  // 0.22.0: the inputs round (kastlan's, keksdose's and Kurvenschmiede's audits).
@@ -971,15 +980,18 @@ export { NotificationTypesSetting } from "./notifications/notification-types-set
971
980
  export type { NotificationTypesSettingProps } from "./notifications/notification-types-setting";
972
981
  export { QuietHoursSetting } from "./notifications/quiet-hours-setting";
973
982
  export type { QuietHoursSettingProps } from "./notifications/quiet-hours-setting";
974
- export { TimeZoneSelect, timeZoneOptions, deviceTimeZone } from "./notifications/time-zone-select";
975
- export type { TimeZoneSelectProps } from "./notifications/time-zone-select";
976
983
  // 0.46 (docs/notifications-harmonization.md §10.1, §10.3): the inbox list, the bell and
977
984
  // the device-zone report (the account's zone is automatic only, amended 2026-10-20).
978
985
  export { NotificationInbox, INBOX_BADGE_INTERVAL } from "./notifications/notification-inbox";
979
- export type { NotificationInboxItem, NotificationInboxProps } from "./notifications/notification-inbox";
986
+ export type {
987
+ NotificationInboxAction,
988
+ NotificationInboxItem,
989
+ NotificationInboxProps,
990
+ } from "./notifications/notification-inbox";
980
991
  export { NotificationBell } from "./notifications/notification-bell";
981
992
  export type { NotificationBellProps } from "./notifications/notification-bell";
982
- export { useDeviceZoneReport } from "./notifications/device-zone-report";
993
+ // `deviceTimeZone` lives beside the report since 0.46.1 (keksdose N6; sign-up needs it).
994
+ export { useDeviceZoneReport, deviceTimeZone } from "./notifications/device-zone-report";
983
995
  export type { DeviceZoneReportOptions } from "./notifications/device-zone-report";
984
996
  export {
985
997
  usePushDevice,
@@ -1016,8 +1028,10 @@ export type { PushDevicesSettingProps } from "./notifications/push-devices-setti
1016
1028
  export { UnsubscribePanel, unsubscribeTokenType } from "./notifications/unsubscribe-panel";
1017
1029
  export type { UnsubscribePanelProps } from "./notifications/unsubscribe-panel";
1018
1030
  // 0.38.0 (docs/feedback-harmonization.md §9.11): the two feedback types' setting words.
1031
+ // 0.46.1 (keksdose N9): `useFeedbackNotificationTypesLabels`, the words resolved, for `adminGroup`.
1019
1032
  export {
1020
1033
  useFeedbackNotificationTypes,
1034
+ useFeedbackNotificationTypesLabels,
1021
1035
  DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS,
1022
1036
  } from "./notifications/feedback-notification-types";
1023
1037
  export type {
@@ -1,7 +1,6 @@
1
1
  import { useEffect } from "react";
2
2
 
3
3
  import { settle } from "../account/account-parts";
4
- import { deviceTimeZone } from "./time-zone-select";
5
4
 
6
5
  export interface DeviceZoneReportOptions {
7
6
  /** The account's `device_timezone` from `/auth/me`; `undefined` while it loads (no
@@ -17,6 +16,20 @@ export interface DeviceZoneReportOptions {
17
16
  account?: string | number | null;
18
17
  }
19
18
 
19
+ /**
20
+ * This device's IANA zone ("Europe/Zurich"), or `"UTC"` where the runtime names none —
21
+ * the zone `useDeviceZoneReport` reports, which becomes the account's (§10.3, automatic
22
+ * only), and the one sign-up sends (`RegisterRequest.device_timezone`). Here since 0.46.1
23
+ * (keksdose N6).
24
+ */
25
+ export function deviceTimeZone(): string {
26
+ try {
27
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
28
+ } catch {
29
+ return "UTC";
30
+ }
31
+ }
32
+
20
33
  /** The (account, stored, device) triples already reported in this start of the app. */
21
34
  const reportedThisStart = new Set<string>();
22
35
 
@@ -30,7 +30,8 @@ export type FeedbackNotificationTypeId = "feedback_answered" | "feedback_comment
30
30
  * - **"New feedback and reworks"** (`feedback_received`, 0.46, feedback §11): to the
31
31
  * admins, when someone files a report or sends one back for rework. (A crash is mailed
32
32
  * only inside the bundle where an app runs the queue, so the text names no crash.)
33
- * `labels.adminGroup` ("Admin") is a heading an app may put over it.
33
+ * `labels.adminGroup` ("Admin") is a heading an app may put over it — read it with
34
+ * {@link useFeedbackNotificationTypesLabels} (0.46.1) for the app's own `groups`.
34
35
  *
35
36
  * Every entry is always returned: the card draws a type only when the server answered
36
37
  * it (notifications §2.1), so kastlan, which does not declare `feedback_comment` while its
@@ -51,3 +52,20 @@ export function useFeedbackNotificationTypes(
51
52
  [words],
52
53
  );
53
54
  }
55
+
56
+ /**
57
+ * The `feedbackNotificationTypes` words, resolved — English, then the provider, then
58
+ * `labels` (0.46.1, keksdose N9): what an app reads to head its own `groups` with
59
+ * `adminGroup` ("Admin") in the provider's locale, as {@link useFeedbackNotificationTypes}
60
+ * labels the entries.
61
+ *
62
+ * ```tsx
63
+ * const { adminGroup } = useFeedbackNotificationTypesLabels();
64
+ * groups={[...appGroups, { id: "admin", label: adminGroup, types: ["feedback_received"] }]}
65
+ * ```
66
+ */
67
+ export function useFeedbackNotificationTypesLabels(
68
+ labels?: LabelOverride<FeedbackNotificationTypesLabels>,
69
+ ): FeedbackNotificationTypesLabels {
70
+ return useKitLabels("feedbackNotificationTypes", DEFAULT_FEEDBACK_NOTIFICATION_TYPES_LABELS, labels);
71
+ }
@@ -20,7 +20,7 @@ export interface NotificationBellProps<Id extends string | number = string | num
20
20
  /** Every unread row of the account (`unread_count`): the badge. */
21
21
  unreadCount: number;
22
22
  /** Open a row: the app marks it read and routes to `item.url` (kastlan switches the
23
- * acting company first). The panel closes. */
23
+ * acting company first, to `item.company_id`). The panel closes. */
24
24
  onOpen: (item: NotificationInboxItem<Id>) => void;
25
25
  /** The app's inbox page or card, for "All notifications". */
26
26
  href: string;
@@ -38,8 +38,15 @@ export interface NotificationInboxItem<Id extends string | number = string | num
38
38
  /** 0.46 (server-kit 0.16 `InboxItem.company`): the company the row is about, named on
39
39
  * the row — kastlan's inbox holds every company's rows. */
40
40
  company?: string | null;
41
+ /** 0.46.1 (server-kit 0.16.1 `InboxItem.company_id`): the same company by its id, so
42
+ * `onOpen` switches to it exactly (two companies may share a name). */
43
+ company_id?: number | string | null;
41
44
  }
42
45
 
46
+ /** The inbox request that failed, as `describeError` gets it (0.46.1): one row marked read,
47
+ * "Mark all as read", or "Clear all". */
48
+ export type NotificationInboxAction = "read" | "readAll" | "clear";
49
+
43
50
  export interface NotificationInboxProps<Id extends string | number = string | number>
44
51
  extends Omit<ComponentPropsWithRef<"div">, "children" | "title"> {
45
52
  /** The rows, newest first; `undefined` while the first page loads. */
@@ -53,7 +60,7 @@ export interface NotificationInboxProps<Id extends string | number = string | nu
53
60
  onClear: () => Promise<unknown> | void;
54
61
  /**
55
62
  * Open a row: the kit marks it read first, then calls this, and the app routes to
56
- * `item.url` (kastlan switches the acting company first). Every row is a button then.
63
+ * `item.url` (kastlan switches the acting company, `item.company_id`, first). Every row is a button then.
57
64
  * Left out, a row with a `url` is a real link — an in-app path through the provider's
58
65
  * router link, an absolute URL in a new tab — that marks itself read on a click or a
59
66
  * middle click (keksdose #451), and a row without one marks itself read.
@@ -72,8 +79,11 @@ export interface NotificationInboxProps<Id extends string | number = string | nu
72
79
  formatDate?: (iso: string) => string;
73
80
  /** The heading. Default `labels.inboxTitle` ("Inbox"). */
74
81
  title?: ReactNode;
75
- /** The app's own words for a failed request, or `undefined` for the kit's `failed`. */
76
- describeError?: (error: unknown) => ReactNode | undefined;
82
+ /** The app's own words for a failed request, or `undefined` for the kit's `failed`.
83
+ * `action` (0.46.1) says which request failed: `"read"` (one row), `"readAll"` ("Mark
84
+ * all as read") or `"clear"` ("Clear all") — so a failed mark-read is not worded as a
85
+ * failed clear. */
86
+ describeError?: (error: unknown, action: NotificationInboxAction) => ReactNode | undefined;
77
87
  labels?: LabelOverride<NotificationsLabels>;
78
88
  }
79
89
 
@@ -141,7 +151,7 @@ export function NotificationInbox<Id extends string | number = string | number>(
141
151
  (error: unknown) => {
142
152
  if (!mounted.current) return;
143
153
  setBusy(null);
144
- setFailure(describeFailure(describeError?.(error), labels.failed));
154
+ setFailure(describeFailure(describeError?.(error, which === "read" ? "readAll" : "clear"), labels.failed));
145
155
  },
146
156
  );
147
157
  };
@@ -150,7 +160,7 @@ export function NotificationInbox<Id extends string | number = string | number>(
150
160
  const markOne = (item: NotificationInboxItem<Id>) => {
151
161
  if (item.read_at !== null) return;
152
162
  settle(() => onMarkRead([item.id])).catch((error: unknown) => {
153
- if (mounted.current) setFailure(describeFailure(describeError?.(error), labels.failed));
163
+ if (mounted.current) setFailure(describeFailure(describeError?.(error, "read"), labels.failed));
154
164
  });
155
165
  };
156
166
 
@@ -68,9 +68,6 @@ export interface NotificationSettingsPatch<Id extends string = string> {
68
68
  digest_cadence?: DigestCadence;
69
69
  quiet_hours_start?: string | null;
70
70
  quiet_hours_end?: string | null;
71
- /** @deprecated 0.46 — the zone is the account's, automatic only (§10.3), and server-kit 0.16 refuses it here;
72
- * removed in 0.47. No kit card sends it. */
73
- timezone?: string;
74
71
  }
75
72
 
76
73
  /** One registered push device, as `GET /notifications/devices` lists it (§3.5), oldest
@@ -21,7 +21,7 @@ export interface NotificationRegionLabels {
21
21
  /**
22
22
  * Every string the notification parts render — the `notifications` namespace of
23
23
  * `<UiKitProvider labels>`, overridable per instance through `labels`: the types card,
24
- * quiet hours and the zone picker, this device's push, the device list, and the
24
+ * quiet hours, this device's push, the device list, and the
25
25
  * unsubscribe page. Cancel is `common.cancel`.
26
26
  */
27
27
  export interface NotificationsLabels {
@@ -30,9 +30,6 @@ export interface NotificationsLabels {
30
30
  typesTitle: string;
31
31
  /** The line under it. */
32
32
  typesDescription: string;
33
- /** @deprecated 0.46 — the card's channels are columns now (`channelEmail`); removed in 0.47. Unused,
34
- * kept so an app's override still type-checks. */
35
- byEmail: string;
36
33
  /** 0.46 (§10.2): the channel columns' headings, and each switch's name. */
37
34
  channelApp: string;
38
35
  channelPush: string;
@@ -64,15 +61,7 @@ export interface NotificationsLabels {
64
61
  quietUntil: string;
65
62
  /** One time set, the other not: quiet hours fail open until both are. */
66
63
  quietIncomplete: string;
67
- /** The zone picker's label. */
68
- timeZone: string;
69
- /** @deprecated 0.46 — the zone is automatic (§10.3), the card offers no device zone; removed in 0.47.
70
- * Unused, kept so an app's override still type-checks. */
71
- deviceZone: (zone: string) => string;
72
- /** @deprecated 0.46 — the zone is automatic (§10.3), nothing to take; removed in 0.47. Unused, kept
73
- * so an app's override still type-checks. */
74
- useDeviceZone: string;
75
- /** The zone's region headings, in the picker and in a zone's name ("Europe / Zurich"). */
64
+ /** The zone's region headings, in a zone's name ("Europe / Zurich"). */
76
65
  regions: NotificationRegionLabels;
77
66
 
78
67
  /* ── This device ────────────────────────────────────────────────────── */
@@ -185,7 +174,6 @@ export interface NotificationsLabels {
185
174
  export const DEFAULT_NOTIFICATIONS_LABELS: NotificationsLabels = {
186
175
  typesTitle: "Notify me about",
187
176
  typesDescription: "Choose what you hear about, and how.",
188
- byEmail: "By e-mail",
189
177
  channelApp: "In the app",
190
178
  channelPush: "Push",
191
179
  channelEmail: "E-mail",
@@ -209,9 +197,6 @@ export const DEFAULT_NOTIFICATIONS_LABELS: NotificationsLabels = {
209
197
  quietFrom: "From",
210
198
  quietUntil: "Until",
211
199
  quietIncomplete: "Set both a start and an end time. Quiet hours take effect once both are set.",
212
- timeZone: "Time zone",
213
- deviceZone: (zone) => `This device is in ${zone}.`,
214
- useDeviceZone: "Use it",
215
200
  regions: {
216
201
  Africa: "Africa",
217
202
  America: "Americas",
@@ -10,22 +10,29 @@ import { CARD_DESCRIPTION_CLASS } from "../settings/settings-card-classes";
10
10
  import { SettingsCardTitle } from "../settings/settings-heading";
11
11
  import { settle, useMounted } from "../account/account-parts";
12
12
  import { describeFailure, FailureLine, useNotificationsLabels, useWriteOverlay } from "./notification-parts";
13
- import type { NotificationsLabels } from "./notifications-labels";
13
+ import type { NotificationRegionLabels, NotificationsLabels } from "./notifications-labels";
14
14
  import type { NotificationSettingsPatch } from "./notification-types";
15
- import { zoneName } from "./time-zone-select";
16
15
 
17
16
  /** The zone the server applies while no device ever reported one (§10.3): every app's. */
18
17
  const DEFAULT_FALLBACK_ZONE = "Europe/Zurich";
19
18
 
19
+ /** A zone for a sentence: "Europe / Zurich", the region in the kit's words (`labels.regions`);
20
+ * a region the labels do not know keeps its IANA name, a zone with none ("UTC") is itself. */
21
+ function zoneName(zone: string, regions: NotificationRegionLabels): string {
22
+ const slash = zone.indexOf("/");
23
+ if (slash < 0) return zone;
24
+ const region = zone.slice(0, slash);
25
+ const named = Object.prototype.hasOwnProperty.call(regions, region)
26
+ ? regions[region as keyof NotificationRegionLabels]
27
+ : region;
28
+ return `${named} / ${zone.slice(slash + 1).replace(/_/g, " ").replace(/\//g, " / ")}`;
29
+ }
30
+
20
31
  export interface QuietHoursSettingProps extends Omit<ComponentPropsWithRef<"div">, "children" | "onChange"> {
21
32
  /** The `GET`'s `quiet_hours_start`, `"HH:MM"`, or `null`. */
22
33
  start: string | null;
23
34
  /** The `GET`'s `quiet_hours_end`. */
24
35
  end: string | null;
25
- /** The zone the 0.36 card showed, an IANA name, or `null`.
26
- * @deprecated 0.46 — pass `zone` (§10.3: the zone is the account's, automatic only); removed in 0.47.
27
- * Shown read-only while `zone` is left out; the card no longer offers a picker. */
28
- timezone?: string | null;
29
36
  /**
30
37
  * 0.46 (§10.3, amended 2026-10-20: automatic only): the account's zone — `/auth/me`'s
31
38
  * `device_timezone`, the zone of the device last used; `null` while no device has
@@ -43,7 +50,7 @@ export interface QuietHoursSettingProps extends Omit<ComponentPropsWithRef<"div"
43
50
  * rejects.
44
51
  */
45
52
  onChange: (patch: NotificationSettingsPatch) => Promise<unknown> | void;
46
- /** The zone named while neither `zone` nor `timezone` names one: the one the server
53
+ /** The zone named while `zone` names none: the one the server
47
54
  * applies. Default `"Europe/Zurich"`, every app's. */
48
55
  fallbackZone?: string;
49
56
  /** The app keeps an inbox, and the description says a held push still reaches it. */
@@ -69,15 +76,13 @@ type QuietField = "quiet_hours_start" | "quiet_hours_end";
69
76
  *
70
77
  * - **Two times**, each saved as it is entered; a cleared time sends `null`. While only
71
78
  * one is set the card says so: the window fails open until both are.
72
- * - **The zone** shows while a time is set: `zone`, else the deprecated `timezone`, else
73
- * `fallbackZone`.
79
+ * - **The zone** shows while a time is set: `zone`, else `fallbackZone`.
74
80
  *
75
81
  * Under a write lock the fields are `commit` controls exempt from billing.
76
82
  */
77
83
  export function QuietHoursSetting({
78
84
  start,
79
85
  end,
80
- timezone = null,
81
86
  zone,
82
87
  held = false,
83
88
  onChange,
@@ -99,7 +104,7 @@ export function QuietHoursSetting({
99
104
  overlay.has(field) ? (overlay.get(field) ?? null) : server;
100
105
  const startShown = shown("quiet_hours_start", start);
101
106
  const endShown = shown("quiet_hours_end", end);
102
- const accountZone = (zone === undefined ? timezone : zone) ?? fallbackZone;
107
+ const accountZone = zone ?? fallbackZone;
103
108
 
104
109
  const save = (patch: NotificationSettingsPatch) => {
105
110
  const fields = Object.keys(patch) as QuietField[];
@@ -23,6 +23,30 @@ export function stampAfter(previous: unknown): string {
23
23
  return new Date(Number.isFinite(prior) && prior >= now ? prior + 1 : now).toISOString();
24
24
  }
25
25
 
26
+ /**
27
+ * A name-based UUID, version 5 (RFC 4122 §4.3: SHA-1 of the namespace's 16 bytes and the
28
+ * name's UTF-8), the same as Python's `uuid.uuid5(uuid.UUID(namespace), name)` — for an id
29
+ * the device and the server derive alike (§2: the per-owner vote id
30
+ * `uuid5(namespace, "<item>:<invitation>")`). Lower case, hyphenated. `namespace` is a UUID,
31
+ * hyphenated or not, in either case; anything else is a `TypeError`. Uses `crypto.subtle`
32
+ * (a secure context: https or localhost).
33
+ */
34
+ export async function uuid5(namespace: string, name: string): Promise<string> {
35
+ const hex = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(namespace)
36
+ ? namespace.replaceAll("-", "")
37
+ : null;
38
+ if (!hex) throw new TypeError(`uuid5: the namespace must be a UUID, not ${JSON.stringify(namespace)}`);
39
+ const nameBytes = new TextEncoder().encode(name);
40
+ const input = new Uint8Array(16 + nameBytes.length);
41
+ for (let i = 0; i < 16; i += 1) input[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
42
+ input.set(nameBytes, 16);
43
+ const bytes = new Uint8Array(await crypto.subtle.digest("SHA-1", input)).slice(0, 16);
44
+ bytes[6] = (bytes[6] & 0x0f) | 0x50; // version 5
45
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // the RFC 4122 variant
46
+ const out = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
47
+ return `${out.slice(0, 8)}-${out.slice(8, 12)}-${out.slice(12, 16)}-${out.slice(16, 20)}-${out.slice(20)}`;
48
+ }
49
+
26
50
  /**
27
51
  * The connection, not the server, failed: the request had no answer (an axios-like error
28
52
  * without `response`, or `fetch`'s `TypeError`), or the session refresh marked a 401 as
package/src/offline.ts CHANGED
@@ -29,4 +29,4 @@ export {
29
29
  } from "./offline/shared-computer";
30
30
  export type { SharedComputerChoiceProps, SharedComputerLabels, SharedComputerStorage } from "./offline/shared-computer";
31
31
  // 0.46 (§2.3): the leaf utilities, shared with `@eifi1/ui-kit/sync` from one module that imports no peer.
32
- export { isOfflineError, readLocalFirst, stampAfter, writeLocalFirst } from "./offline/local-first";
32
+ export { isOfflineError, readLocalFirst, stampAfter, uuid5, writeLocalFirst } from "./offline/local-first";
package/src/sync/db.ts CHANGED
@@ -79,6 +79,28 @@ export const SYNC_DB_STORES = {
79
79
  meta: "key",
80
80
  } as const;
81
81
 
82
+ /**
83
+ * The `rows` schema with an `[entity+pk]` index (0.46.1), opt-in: the D16 pages start from a
84
+ * server id (`/meters/7`) and {@link rowByPk} finds its copy row by this index. Not in
85
+ * {@link SYNC_DB_STORES}, so no database on a device changes until its app names it in a
86
+ * version of its own — kastlan, whose devices are on v2: `upgrades: [..., { version: 3,
87
+ * stores: SYNC_DB_PK_INDEX }]`. A database new to the devices may take it at once:
88
+ * `extraStores: { ...SYNC_DB_PK_INDEX }`.
89
+ */
90
+ export const SYNC_DB_PK_INDEX = {
91
+ rows: "[entity+id], entity, [entity+pk]",
92
+ } as const;
93
+
94
+ /**
95
+ * The copy row of the server's id `pk` in `entity` (0.46.1): by the `[entity+pk]` index when
96
+ * the database has it ({@link SYNC_DB_PK_INDEX}), else by a scan of the entity's rows.
97
+ * `undefined` when the copy holds no such row.
98
+ */
99
+ export async function rowByPk(db: SyncDb, entity: string, pk: number): Promise<SyncedRow | undefined> {
100
+ if (db.rows.schema.idxByName["[entity+pk]"]) return db.rows.where("[entity+pk]").equals([entity, pk]).first();
101
+ return db.rows.where("entity").equals(entity).filter((row) => row.pk === pk).first();
102
+ }
103
+
82
104
  /**
83
105
  * Open (lazily, on first use) the sync database `name`: the engine's tables plus the app's
84
106
  * `extraStores`, at version 1 or the last of `upgrades`. One database per account (kastlan: