@eifi1/ui-kit 0.52.0 → 0.54.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 (290) hide show
  1. package/README.md +52 -59
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/auth/auth-errors.d.ts +36 -0
  5. package/dist/auth/auth-errors.js +20 -0
  6. package/dist/auth/auth-errors.js.map +1 -1
  7. package/dist/billing/cancel-subscription.d.ts +5 -14
  8. package/dist/billing/cancel-subscription.js +1 -3
  9. package/dist/billing/cancel-subscription.js.map +1 -1
  10. package/dist/billing/plan-card.d.ts +5 -13
  11. package/dist/billing/plan-card.js +3 -11
  12. package/dist/billing/plan-card.js.map +1 -1
  13. package/dist/billing/plan-switch.d.ts +5 -14
  14. package/dist/billing/plan-switch.js +1 -3
  15. package/dist/billing/plan-switch.js.map +1 -1
  16. package/dist/chart.d.ts +3 -14
  17. package/dist/chart.js +64 -11
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/amount-input.d.ts +2 -1
  20. package/dist/components/amount-input.js.map +1 -1
  21. package/dist/components/calendar-heatmap.js +4 -2
  22. package/dist/components/calendar-heatmap.js.map +1 -1
  23. package/dist/components/currency-option-list.d.ts +2 -1
  24. package/dist/components/currency-option-list.js +10 -3
  25. package/dist/components/currency-option-list.js.map +1 -1
  26. package/dist/components/currency-select.d.ts +3 -1
  27. package/dist/components/currency-select.js.map +1 -1
  28. package/dist/components/danger-confirm.d.ts +22 -1
  29. package/dist/components/danger-confirm.js +50 -28
  30. package/dist/components/danger-confirm.js.map +1 -1
  31. package/dist/components/data-table-filter-popover.d.ts +2 -1
  32. package/dist/components/data-table-filter-popover.js +3 -2
  33. package/dist/components/data-table-filter-popover.js.map +1 -1
  34. package/dist/components/data-table-filters.d.ts +33 -3
  35. package/dist/components/data-table-filters.js +6 -4
  36. package/dist/components/data-table-filters.js.map +1 -1
  37. package/dist/components/date-field.d.ts +1 -1
  38. package/dist/components/date-field.js.map +1 -1
  39. package/dist/components/date-picker.d.ts +1 -1
  40. package/dist/components/date-picker.js.map +1 -1
  41. package/dist/components/date-range-picker.d.ts +4 -17
  42. package/dist/components/date-range-picker.js +2 -2
  43. package/dist/components/date-range-picker.js.map +1 -1
  44. package/dist/components/dialog-frame.d.ts +3 -1
  45. package/dist/components/dialog-frame.js +6 -2
  46. package/dist/components/dialog-frame.js.map +1 -1
  47. package/dist/components/mini-calendar.js +3 -2
  48. package/dist/components/mini-calendar.js.map +1 -1
  49. package/dist/components/modal.d.ts +27 -0
  50. package/dist/components/modal.js +5 -3
  51. package/dist/components/modal.js.map +1 -1
  52. package/dist/components/nav-pills.d.ts +1 -1
  53. package/dist/components/nav-pills.js.map +1 -1
  54. package/dist/components/number-input.js.map +1 -1
  55. package/dist/components/series-chart-axes.d.ts +1 -1
  56. package/dist/components/series-chart-axes.js.map +1 -1
  57. package/dist/components/series-chart.d.ts +64 -19
  58. package/dist/components/series-chart.js +2 -1
  59. package/dist/components/series-chart.js.map +1 -1
  60. package/dist/components/stat-tile.d.ts +21 -2
  61. package/dist/components/stat-tile.js +4 -2
  62. package/dist/components/stat-tile.js.map +1 -1
  63. package/dist/components/treemap-labels.d.ts +6 -1
  64. package/dist/components/treemap-labels.js +2 -2
  65. package/dist/components/treemap-labels.js.map +1 -1
  66. package/dist/components/treemap.d.ts +29 -4
  67. package/dist/components/treemap.js +22 -9
  68. package/dist/components/treemap.js.map +1 -1
  69. package/dist/currencies.d.ts +16 -2
  70. package/dist/currencies.js +2 -1
  71. package/dist/currencies.js.map +1 -1
  72. package/dist/data-table.d.ts +1 -6
  73. package/dist/data-table.js +1 -9
  74. package/dist/data-table.js.map +1 -1
  75. package/dist/demo/demo-countdown.d.ts +0 -5
  76. package/dist/demo/demo-countdown.js +0 -4
  77. package/dist/demo/demo-countdown.js.map +1 -1
  78. package/dist/eslint/index.js +1 -1
  79. package/dist/feedback/feedback-context-labels.js +2 -1
  80. package/dist/feedback/feedback-context-labels.js.map +1 -1
  81. package/dist/feedback/feedback-context.d.ts +19 -3
  82. package/dist/feedback/feedback-context.js +12 -1
  83. package/dist/feedback/feedback-context.js.map +1 -1
  84. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  85. package/dist/feedback/feedback-diagnostics.js +27 -0
  86. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  87. package/dist/feedback/feedback-dialog.d.ts +10 -0
  88. package/dist/feedback/feedback-dialog.js +6 -4
  89. package/dist/feedback/feedback-dialog.js.map +1 -1
  90. package/dist/feedback/feedback-mutation.d.ts +11 -23
  91. package/dist/feedback/feedback-mutation.js +1 -9
  92. package/dist/feedback/feedback-mutation.js.map +1 -1
  93. package/dist/feedback/feedback-public-list.d.ts +1 -6
  94. package/dist/feedback/feedback-public-list.js +1 -2
  95. package/dist/feedback/feedback-public-list.js.map +1 -1
  96. package/dist/feedback/feedback-record.d.ts +5 -0
  97. package/dist/feedback/feedback-record.js.map +1 -1
  98. package/dist/feedback/feedback-row-detail-labels.js +1 -8
  99. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  100. package/dist/feedback/feedback-row-detail.d.ts +6 -50
  101. package/dist/feedback/feedback-row-detail.js +3 -8
  102. package/dist/feedback/feedback-row-detail.js.map +1 -1
  103. package/dist/feedback/feedback-status-undo.d.ts +3 -19
  104. package/dist/feedback/feedback-status-undo.js.map +1 -1
  105. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  106. package/dist/feedback/feedback-submit-dialog.js +15 -3
  107. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  108. package/dist/feedback/feedback-submit.d.ts +30 -2
  109. package/dist/feedback/feedback-submit.js +5 -2
  110. package/dist/feedback/feedback-submit.js.map +1 -1
  111. package/dist/feedback/feedback-thread.d.ts +0 -9
  112. package/dist/feedback/feedback-thread.js.map +1 -1
  113. package/dist/feedback.d.ts +6 -40
  114. package/dist/feedback.js +88 -46
  115. package/dist/feedback.js.map +1 -1
  116. package/dist/hooks/use-hotkey.js +11 -1
  117. package/dist/hooks/use-hotkey.js.map +1 -1
  118. package/dist/i18n/defaults.js +0 -4
  119. package/dist/i18n/defaults.js.map +1 -1
  120. package/dist/i18n/german.js +5 -20
  121. package/dist/i18n/german.js.map +1 -1
  122. package/dist/i18n/kit-labels.d.ts +5 -14
  123. package/dist/i18n/kit-labels.js.map +1 -1
  124. package/dist/i18n/locales/en.js +5 -31
  125. package/dist/i18n/locales/en.js.map +3 -3
  126. package/dist/i18n/locales/es.js +5 -20
  127. package/dist/i18n/locales/es.js.map +1 -1
  128. package/dist/i18n/locales/fr.js +5 -20
  129. package/dist/i18n/locales/fr.js.map +1 -1
  130. package/dist/i18n/locales/hu.js +5 -20
  131. package/dist/i18n/locales/hu.js.map +1 -1
  132. package/dist/i18n/locales/it.js +5 -21
  133. package/dist/i18n/locales/it.js.map +1 -1
  134. package/dist/i18n/locales/zh.js +5 -20
  135. package/dist/i18n/locales/zh.js.map +1 -1
  136. package/dist/i18n/review.js +9 -4
  137. package/dist/i18n/review.js.map +1 -1
  138. package/dist/i18n/shared-labels.js +0 -1
  139. package/dist/i18n/shared-labels.js.map +1 -1
  140. package/dist/index.d.ts +18 -67
  141. package/dist/index.js +28 -103
  142. package/dist/index.js.map +1 -1
  143. package/dist/lib/dates.d.ts +7 -1
  144. package/dist/lib/dates.js +8 -1
  145. package/dist/lib/dates.js.map +1 -1
  146. package/dist/lib/format.d.ts +37 -2
  147. package/dist/lib/format.js +25 -1
  148. package/dist/lib/format.js.map +1 -1
  149. package/dist/lib/intl-cache.d.ts +4 -2
  150. package/dist/lib/intl-cache.js.map +1 -1
  151. package/dist/lib/translation-review.d.ts +26 -0
  152. package/dist/lib/translation-review.js +14 -0
  153. package/dist/lib/translation-review.js.map +1 -1
  154. package/dist/rhf/fields.d.ts +2 -2
  155. package/dist/rhf/fields.js.map +1 -1
  156. package/dist/search/search-index.d.ts +0 -6
  157. package/dist/search/search-index.js +0 -4
  158. package/dist/search/search-index.js.map +1 -1
  159. package/dist/search.d.ts +1 -8
  160. package/dist/search.js +2 -7
  161. package/dist/search.js.map +1 -1
  162. package/dist/shell/account-menu-language.d.ts +7 -21
  163. package/dist/shell/account-menu-language.js +1 -3
  164. package/dist/shell/account-menu-language.js.map +1 -1
  165. package/dist/shell/account-menu-text-size.d.ts +9 -23
  166. package/dist/shell/account-menu-text-size.js +2 -3
  167. package/dist/shell/account-menu-text-size.js.map +1 -1
  168. package/dist/shell/app-shell.d.ts +0 -10
  169. package/dist/shell/app-shell.js +1 -7
  170. package/dist/shell/app-shell.js.map +1 -1
  171. package/dist/shell/company-switcher.d.ts +1 -1
  172. package/dist/shell/company-switcher.js.map +1 -1
  173. package/dist/shell/dev-menu.d.ts +1 -1
  174. package/dist/shell/dev-menu.js.map +1 -1
  175. package/dist/shell/top-bar-parts-labels.d.ts +1 -1
  176. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  177. package/dist/shell.d.ts +7 -29
  178. package/dist/shell.js +12 -42
  179. package/dist/shell.js.map +1 -1
  180. package/dist/sync.d.ts +2 -6
  181. package/dist/sync.js +2 -15
  182. package/dist/sync.js.map +1 -1
  183. package/dist/theme/chart-palette.d.ts +0 -5
  184. package/dist/theme/chart-palette.js +0 -2
  185. package/dist/theme/chart-palette.js.map +1 -1
  186. package/package.json +1 -1
  187. package/src/account/delete-account-setting.tsx +3 -2
  188. package/src/auth/auth-errors.ts +69 -0
  189. package/src/billing/cancel-subscription.tsx +5 -16
  190. package/src/billing/plan-card.tsx +7 -24
  191. package/src/billing/plan-switch.tsx +5 -16
  192. package/src/chart.ts +62 -13
  193. package/src/components/amount-input.tsx +2 -1
  194. package/src/components/calendar-heatmap.tsx +8 -2
  195. package/src/components/currency-option-list.tsx +12 -4
  196. package/src/components/currency-select.tsx +3 -1
  197. package/src/components/danger-confirm.tsx +80 -27
  198. package/src/components/data-table-filter-popover.tsx +19 -11
  199. package/src/components/data-table-filters.ts +36 -8
  200. package/src/components/date-field.tsx +1 -1
  201. package/src/components/date-picker.tsx +0 -1
  202. package/src/components/date-range-picker.tsx +6 -22
  203. package/src/components/dialog-frame.tsx +12 -4
  204. package/src/components/mini-calendar.tsx +5 -2
  205. package/src/components/modal.tsx +36 -7
  206. package/src/components/nav-pills.tsx +1 -1
  207. package/src/components/number-input.tsx +4 -3
  208. package/src/components/series-chart-axes.ts +6 -1
  209. package/src/components/series-chart.tsx +78 -21
  210. package/src/components/stat-tile.tsx +32 -3
  211. package/src/components/treemap-labels.ts +11 -3
  212. package/src/components/treemap.tsx +65 -12
  213. package/src/currencies.ts +23 -3
  214. package/src/data-table.ts +1 -11
  215. package/src/demo/demo-countdown.ts +3 -8
  216. package/src/feedback/feedback-context-labels.ts +1 -0
  217. package/src/feedback/feedback-context.tsx +34 -2
  218. package/src/feedback/feedback-diagnostics.tsx +41 -0
  219. package/src/feedback/feedback-dialog.tsx +16 -4
  220. package/src/feedback/feedback-mutation.ts +12 -33
  221. package/src/feedback/feedback-public-list.tsx +1 -7
  222. package/src/feedback/feedback-record.ts +5 -0
  223. package/src/feedback/feedback-row-detail-labels.ts +1 -8
  224. package/src/feedback/feedback-row-detail.tsx +21 -58
  225. package/src/feedback/feedback-status-undo.ts +4 -18
  226. package/src/feedback/feedback-submit-dialog.tsx +15 -2
  227. package/src/feedback/feedback-submit.tsx +36 -3
  228. package/src/feedback/feedback-thread.tsx +0 -10
  229. package/src/feedback.ts +89 -48
  230. package/src/hooks/use-hotkey.ts +24 -1
  231. package/src/i18n/defaults.ts +0 -4
  232. package/src/i18n/german.ts +4 -19
  233. package/src/i18n/kit-labels.tsx +5 -14
  234. package/src/i18n/locales/es.ts +4 -19
  235. package/src/i18n/locales/fr.ts +4 -19
  236. package/src/i18n/locales/hu.ts +4 -19
  237. package/src/i18n/locales/it.ts +4 -20
  238. package/src/i18n/locales/zh.ts +4 -19
  239. package/src/i18n/review.ts +9 -4
  240. package/src/i18n/shared-labels.ts +0 -1
  241. package/src/index.ts +44 -114
  242. package/src/lib/dates.ts +24 -2
  243. package/src/lib/format.ts +66 -3
  244. package/src/lib/intl-cache.ts +4 -2
  245. package/src/lib/translation-review.ts +52 -0
  246. package/src/rhf/fields.tsx +2 -2
  247. package/src/search/search-index.ts +0 -15
  248. package/src/search.ts +8 -8
  249. package/src/shell/account-menu-language.tsx +7 -27
  250. package/src/shell/account-menu-text-size.tsx +13 -32
  251. package/src/shell/app-shell.tsx +0 -13
  252. package/src/shell/company-switcher.tsx +1 -1
  253. package/src/shell/dev-menu.tsx +1 -1
  254. package/src/shell/top-bar-parts-labels.ts +1 -1
  255. package/src/shell.ts +17 -44
  256. package/src/sync.ts +2 -15
  257. package/src/theme/chart-palette.ts +0 -2
  258. package/tokens.css +4 -4
  259. package/dist/billing/dialog-close.d.ts +0 -29
  260. package/dist/billing/dialog-close.js +0 -8
  261. package/dist/billing/dialog-close.js.map +0 -1
  262. package/dist/components/field-sync-labels.d.ts +0 -3
  263. package/dist/components/field-sync-labels.js +0 -12
  264. package/dist/components/field-sync-labels.js.map +0 -1
  265. package/dist/components/field-sync.d.ts +0 -186
  266. package/dist/components/field-sync.js +0 -269
  267. package/dist/components/field-sync.js.map +0 -1
  268. package/dist/components/section-nav.d.ts +0 -44
  269. package/dist/components/section-nav.js +0 -33
  270. package/dist/components/section-nav.js.map +0 -1
  271. package/dist/components/sign-chip-labels.d.ts +0 -3
  272. package/dist/components/sign-chip-labels.js +0 -11
  273. package/dist/components/sign-chip-labels.js.map +0 -1
  274. package/dist/components/sign-chip.d.ts +0 -77
  275. package/dist/components/sign-chip.js +0 -47
  276. package/dist/components/sign-chip.js.map +0 -1
  277. package/dist/lib/isin.d.ts +0 -38
  278. package/dist/lib/isin.js +0 -36
  279. package/dist/lib/isin.js.map +0 -1
  280. package/dist/shell/role-switcher.d.ts +0 -28
  281. package/dist/shell/role-switcher.js +0 -31
  282. package/dist/shell/role-switcher.js.map +0 -1
  283. package/src/billing/dialog-close.ts +0 -34
  284. package/src/components/field-sync-labels.ts +0 -14
  285. package/src/components/field-sync.tsx +0 -504
  286. package/src/components/section-nav.tsx +0 -72
  287. package/src/components/sign-chip-labels.ts +0 -13
  288. package/src/components/sign-chip.tsx +0 -133
  289. package/src/lib/isin.ts +0 -72
  290. package/src/shell/role-switcher.tsx +0 -53
@@ -129,13 +129,26 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
129
129
  * A tile's and a group's accessible name (`labels.tile`, `labels.group`, 0.52) says the
130
130
  * value too, asked of the formatter as the tooltip asks it, share and all, and its text
131
131
  * is used; markup is said as the bare number. The name says the share itself, so a
132
- * formatter that writes it into its text has it said twice.
132
+ * formatter that writes it into its text has it said twice — say so with
133
+ * {@link TreemapProps.shareInValue}.
133
134
  *
134
135
  * Default: the bare number through `Intl.NumberFormat` in the provider's locale (0.52,
135
136
  * DOC-10: before, the runtime's). A host with a currency passes this — the kit knows
136
137
  * none.
137
138
  */
138
139
  valueFormatter?: (value: number, share: number | undefined) => ReactNode;
140
+ /**
141
+ * The `valueFormatter`'s text already says the share ("CHF 412.30 · 12%"), so a tile's
142
+ * and a group's accessible name leave their own " (12%)" out (0.53, keksdose after 0.52):
143
+ * the map hands `labels.tile` and `labels.group` the share as `""`, and every catalogue's
144
+ * template drops it there, in its own typography. Before, the only way out was to replace
145
+ * both templates, and with them the kit's seven catalogues. The tooltip is the same either
146
+ * way: it prints what the formatter prints. A formatter whose value is markup is said as
147
+ * the bare number, so its share is then not said at all.
148
+ *
149
+ * Default `false`: the name says the share.
150
+ */
151
+ shareInValue?: boolean;
139
152
  /**
140
153
  * What the map SHOWS, in a sentence — "Rent is three fifths of the month." — the
141
154
  * figure's accessible description (0.52, DSP-5). Not drawn; a caption the eye should
@@ -185,8 +198,18 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
185
198
  * Every tile is tagged as a whole too, clickable or not (0.52): its accessible name
186
199
  * (`labels.tile`) says the name and the value, in an attribute a host's redaction
187
200
  * reaches only through the tag.
201
+ *
202
+ * - `true`: every node — the tiles, and a group's frame (its `<g>` and its band's name)
203
+ * and tooltip bubble too.
204
+ * - `"tiles"` (0.53, keksdose after 0.52): the tiles as `true` tags them, and no group —
205
+ * for groups that are public names over private ones, as chains (Migros, Coop) over
206
+ * the shops a user paid. Its frame's band, its frame and its bubble's name are
207
+ * untagged, and so is the frame's accessible name (`labels.group`), which says the
208
+ * group's TOTAL: use it only where the groups' names and totals are not the private
209
+ * part. The bubble's value cell is tagged whatever this says, as in every chart.
210
+ * A tile's name still says its group's (`labels.inGroup`), under the tile's tag.
188
211
  */
189
- redactNames?: boolean;
212
+ redactNames?: boolean | "tiles";
190
213
  /**
191
214
  * A second, smaller line under a tile's label — one extra figure per node that the
192
215
  * name cannot carry (a year-on-year `+12%`, `new`). Return `undefined` for a node
@@ -436,7 +459,9 @@ export interface TreemapCellProps {
436
459
  /** Filled in by recharts for a GROUP: the nodes laid out inside it. A cell with
437
460
  * children draws a frame, not a tile (0.52). */
438
461
  children?: readonly object[] | null;
439
- redactNames?: boolean;
462
+ /** See {@link TreemapProps.redactNames}: `"tiles"` tags a cell with no `children`, and
463
+ * leaves a group's frame and band untagged (0.53). */
464
+ redactNames?: boolean | "tiles";
440
465
  nodeNote?: (id: string) => string | undefined;
441
466
  /** The page's reading direction, which `Treemap` reads off its own root. In `rtl`
442
467
  * the label stands in the tile's top RIGHT corner and is shaped right-to-left, so a
@@ -578,6 +603,10 @@ function Tile({
578
603
  // is a button.
579
604
  const roving = path != null && keys ? keys : undefined;
580
605
  const focusable = roving !== undefined || activate !== undefined;
606
+ // `"tiles"` (0.53, keksdose after 0.52) leaves a group's frame and band untagged: its
607
+ // groups are public chain names, and its blur on `[data-private]` blurred them with the
608
+ // payees. A truthy test on the union would have tagged them still.
609
+ const redact = redactNames === true || (redactNames === "tiles" && !group);
581
610
  const onKeyDown = focusable
582
611
  ? (e: KeyboardEvent<SVGGElement>) => {
583
612
  if (roving?.move(path!, e)) {
@@ -606,7 +635,7 @@ function Tile({
606
635
  // In a map, -1 as drawn: the map moves its one stop on the elements in place.
607
636
  tabIndex={roving ? -1 : activate ? 0 : undefined}
608
637
  aria-label={focusable ? (roving?.name(path!) ?? name) : undefined}
609
- data-private={focusable && redactNames ? "" : undefined}
638
+ data-private={focusable && redact ? "" : undefined}
610
639
  data-group={group ? "" : undefined}
611
640
  cursor={activate ? "pointer" : undefined}
612
641
  // CSS beats a presentation attribute, so this recolours the rect's own stroke
@@ -646,7 +675,7 @@ function Tile({
646
675
  <text
647
676
  // A plain attribute, so the host's `[data-private]` rule reaches an SVG
648
677
  // <text> exactly as it reaches a table cell.
649
- data-private={redactNames ? "" : undefined}
678
+ data-private={redact ? "" : undefined}
650
679
  x={textX}
651
680
  // A band's name centred in its 20px; a tile's where its floor leaves room below.
652
681
  y={py + (group ? 14 : 15) * scale}
@@ -697,10 +726,20 @@ const TREEMAP_CONFIG: ChartConfig = {};
697
726
  * recharts CLONES the `content` element to inject `active`/`payload`, so this has to BE
698
727
  * that element — a `<div>` wrapped around `<ChartTooltipContent />` in the JSX would
699
728
  * take those props itself and hand the tooltip nothing.
729
+ *
730
+ * `groups: false` is `redactNames="tiles"` (0.53): a group's bubble is left untagged, as its
731
+ * frame is. The payload says which node is hovered — recharts hands the node itself, with
732
+ * its laid-out `children` for a group and `null` for a tile, as the cell is told — so
733
+ * anything else, or no payload, is tagged.
700
734
  */
701
- function RedactedTooltipContent(props: ComponentProps<typeof ChartTooltipContent>) {
735
+ function RedactedTooltipContent({
736
+ groups = true,
737
+ ...props
738
+ }: ComponentProps<typeof ChartTooltipContent> & { groups?: boolean }) {
739
+ const children = props.payload?.[0]?.payload?.children;
740
+ const group = Array.isArray(children) && children.length > 0;
702
741
  return (
703
- <div data-private>
742
+ <div data-private={groups || !group ? true : undefined}>
704
743
  <ChartTooltipContent {...props} />
705
744
  </div>
706
745
  );
@@ -895,6 +934,7 @@ export function Treemap({
895
934
  onNodeClick,
896
935
  onGroupClick,
897
936
  redactNames = false,
937
+ shareInValue = false,
898
938
  nodeNote,
899
939
  maxTiles,
900
940
  colors,
@@ -1066,10 +1106,13 @@ export function Treemap({
1066
1106
  };
1067
1107
  // A group's name by its path; a node's groups by theirs, its own path's prefixes.
1068
1108
  const groupName = new Map(order.filter((n) => n.children).map((n) => [n.path, n.name]));
1109
+ // `""` when the formatter's text says the share already, which every template drops
1110
+ // (0.53, `shareInValue`).
1111
+ const shareOf = (v: number) => (shareInValue ? "" : shareText(total > 0 ? v / total : 0));
1069
1112
  return new Map(
1070
1113
  order.map((n) => {
1071
1114
  const say = n.children ? labels.group : labels.tile;
1072
- const own = say(n.name, valueText(n.size), shareText(total > 0 ? n.size / total : 0));
1115
+ const own = say(n.name, valueText(n.size), shareOf(n.size));
1073
1116
  // The innermost group first: "Rent: …, in Housing, in Fixed costs".
1074
1117
  const named = n.groups
1075
1118
  .map((g, i) => groupName.get(pathOf(n.groups.slice(0, i), g)) ?? g)
@@ -1077,7 +1120,7 @@ export function Treemap({
1077
1120
  return [n.path, named] as const;
1078
1121
  }),
1079
1122
  );
1080
- }, [order, labels, format, numberFormat, shareText, total]);
1123
+ }, [order, labels, format, numberFormat, shareText, shareInValue, total]);
1081
1124
  const keys = useMemo<TileKeys>(
1082
1125
  () => ({ name: (path) => names.get(path), onFocus, register, move }),
1083
1126
  [names, onFocus, register, move],
@@ -1086,7 +1129,6 @@ export function Treemap({
1086
1129
  () => ({ tokens: tokenHex, edgeFor, edges: edgesInForce, keys, layout, hover: anchor.set }),
1087
1130
  [tokenHex, edgeFor, edgesInForce, keys, layout, anchor],
1088
1131
  );
1089
- const Content = redactNames ? RedactedTooltipContent : ChartTooltipContent;
1090
1132
  const cell = useMemo(
1091
1133
  () => (
1092
1134
  <TreemapCell
@@ -1100,8 +1142,19 @@ export function Treemap({
1100
1142
  [onNodeClick, onGroupClick, redactNames, nodeNote, dir],
1101
1143
  );
1102
1144
  const tooltip = useMemo(
1103
- () => <TreemapTooltip content={<Content hideLabel valueFormatter={format} />} anchor={anchor} />,
1104
- [Content, format, anchor],
1145
+ () => (
1146
+ <TreemapTooltip
1147
+ content={
1148
+ redactNames ? (
1149
+ <RedactedTooltipContent hideLabel valueFormatter={format} groups={redactNames === true} />
1150
+ ) : (
1151
+ <ChartTooltipContent hideLabel valueFormatter={format} />
1152
+ )
1153
+ }
1154
+ anchor={anchor}
1155
+ />
1156
+ ),
1157
+ [redactNames, format, anchor],
1105
1158
  );
1106
1159
 
1107
1160
  if (nodes.length === 0) return null;
package/src/currencies.ts CHANGED
@@ -12,6 +12,11 @@
12
12
  // CurrencySelect, AmountInput's currency chip and NumberInput's code detection read this
13
13
  // same array (components/currency-option-list re-exports it, and currency-select as it
14
14
  // always has), so the list the pickers show and the flags an app ships cannot drift.
15
+ //
16
+ // A currency that was REPLACED stays in the array, marked `retired`: the pickers stop
17
+ // offering it, and whatever reads a value an app already holds — the trigger's flag and
18
+ // code, `getCurrency`, the flag subset, NumberInput's code detection — keeps knowing it
19
+ // (keksdose audit follow-up, 2026-11-01: the lev, after Bulgaria's euro).
15
20
 
16
21
  export interface CurrencyOption {
17
22
  code: string;
@@ -19,6 +24,19 @@ export interface CurrencyOption {
19
24
  symbol: string;
20
25
  /** ISO 3166-1 alpha-2 country code (lowercase) used by flag-icons. */
21
26
  country: string;
27
+ /**
28
+ * The day ("YYYY-MM-DD") the currency was replaced as its country's money — set only on
29
+ * one that was. The pickers leave it out of their default list. A field that already
30
+ * holds it still shows it (the trigger's flag and code, and its row in the list,
31
+ * checked), and an explicit `options` list that names it still offers it: that list is
32
+ * the caller's.
33
+ *
34
+ * Marked rather than deleted, because an app may hold amounts in it from before. Gone
35
+ * from the array, `getCurrency` would answer `undefined` for a stored value, the field
36
+ * would show its placeholder over a real amount, and an app's flag subset
37
+ * (`CURRENCY_COUNTRIES`) would ship that value's flag as an empty box.
38
+ */
39
+ retired?: string;
22
40
  }
23
41
 
24
42
  export const CURRENCIES: CurrencyOption[] = [
@@ -37,7 +55,8 @@ export const CURRENCIES: CurrencyOption[] = [
37
55
  { code: "CZK", name: "Czech Koruna", symbol: "Kč", country: "cz" },
38
56
  { code: "HUF", name: "Hungarian Forint", symbol: "Ft", country: "hu" },
39
57
  { code: "RON", name: "Romanian Leu", symbol: "lei", country: "ro" },
40
- { code: "BGN", name: "Bulgarian Lev", symbol: "лв", country: "bg" },
58
+ // Bulgaria adopted the euro on 2026-01-01 (keksdose audit follow-up, 2026-11-01).
59
+ { code: "BGN", name: "Bulgarian Lev", symbol: "лв", country: "bg", retired: "2026-01-01" },
41
60
  { code: "TRY", name: "Turkish Lira", symbol: "₺", country: "tr" },
42
61
  { code: "RUB", name: "Russian Ruble", symbol: "₽", country: "ru" },
43
62
  { code: "CNY", name: "Chinese Yuan", symbol: "¥", country: "cn" },
@@ -56,8 +75,9 @@ export const CURRENCIES: CurrencyOption[] = [
56
75
  * code flag-icons names it by (`fi-ch`, `flags/4x3/ch.svg`). `CHF` → `"ch"`, `EUR` →
57
76
  * `"eu"` (flag-icons has the EU's flag under that code).
58
77
  *
59
- * Derived from {@link CURRENCIES}, so a currency the pickers gain is a flag here too. An
60
- * app that ships only the flags it renders builds its subset from these values:
78
+ * Derived from {@link CURRENCIES}, so a currency the pickers gain is a flag here too — and
79
+ * a `retired` one keeps its flag, for the values an app still holds in it. An app that
80
+ * ships only the flags it renders builds its subset from these values:
61
81
  *
62
82
  * ```ts
63
83
  * // vite.config.ts — plain Node, no DOM
package/src/data-table.ts CHANGED
@@ -27,22 +27,12 @@ export type {
27
27
  DataTableRowAction,
28
28
  DataTableDesktopSwipeActions,
29
29
  } from "./components/data-table";
30
- // 0.46.1 (keksdose N3): a swipe plan as `RowActions`' actions — the desktop column's own
31
- // derivation, for an app's hand-built actions column.
32
- export {
33
- /** @deprecated 0.52 — `DataTable`'s `desktopSwipeActions`, which builds its column with it (API-4); removed in 0.53 */
34
- swipePlanRowActions,
35
- } from "./components/data-table-row-actions";
36
30
  // 0.48 (keksdose dev #605): a `groupBy` level (also `mobileGroupBy`'s) and the `loadMore` footer.
37
31
  export type { DataTableGroupBy, DataTableLoadMore } from "./components/data-table-groups";
38
32
  // 0.48.2 (keksdose dev #632): `virtualize`'s options.
39
33
  export type { DataTableVirtualize } from "./components/data-table-virtual";
40
34
  export * from "./components/data-table-cells";
41
- export {
42
- useTableUrlState,
43
- /** @deprecated 0.52 — `useTableUrlState`, which reads the URL with it (API-4); removed in 0.53 */
44
- readTableUrlState,
45
- } from "./components/use-table-state";
35
+ export { useTableUrlState } from "./components/use-table-state";
46
36
  export type {
47
37
  TableUrlState,
48
38
  TableUrlSync,
@@ -50,14 +50,9 @@ function minutesAt(expiry: number, now: number): number | null {
50
50
  return Math.max(0, Math.ceil((expiry - now) / MINUTE));
51
51
  }
52
52
 
53
- /**
54
- * The countdown at `now` — pure, for a test or a server-rendered value. See
55
- * {@link DemoCountdown} for the rounding and the thresholds.
56
- */
57
- export function demoCountdown(expiresAt: DemoExpiry | null | undefined, now: number): DemoCountdown {
58
- return countdownOf(minutesAt(expiryMs(expiresAt), now));
59
- }
60
-
53
+ /** The countdown for `minutesLeft` — see {@link DemoCountdown} for the rounding and the
54
+ * thresholds. (Public as `demoCountdown(expiresAt, now)` until 0.53, deprecated in 0.52,
55
+ * API-4: `useDemoCountdown` with its `now` option is the same countdown.) */
61
56
  function countdownOf(minutesLeft: number | null): DemoCountdown {
62
57
  if (minutesLeft === null) {
63
58
  return { minutesLeft, hours: 0, minutes: 0, lastHour: false, warning: false, ended: false };
@@ -7,4 +7,5 @@ import type { FeedbackContextLabels } from "./feedback-context";
7
7
  export const DEFAULT_FEEDBACK_CONTEXT_LABELS: FeedbackContextLabels = {
8
8
  user: "User",
9
9
  attachUrl: "Attach current page URL",
10
+ diagnostics: "Diagnostics sent with the report",
10
11
  };
@@ -1,8 +1,10 @@
1
+ import { useId } from "react";
1
2
  import { Checkbox } from "../components/checkbox";
2
3
  import { DEFAULT_COMMON_LABELS, useKitLabels, type LabelOverride } from "../i18n/kit-labels";
3
4
  import { cn } from "../lib/cn";
4
5
  import type { FeedbackContext, FeedbackEnvironment } from "./feedback-record";
5
6
  import { DEFAULT_FEEDBACK_CONTEXT_LABELS } from "./feedback-context-labels";
7
+ import { diagnosticsEntries, FeedbackDiagnosticsList } from "./feedback-diagnostics";
6
8
 
7
9
  /**
8
10
  * The reporter context of a feedback report: the box the submit dialog shows, and the
@@ -70,6 +72,10 @@ export interface FeedbackContextInput {
70
72
  viewport?: string;
71
73
  /** Default `navigator.userAgent`. */
72
74
  ua?: string;
75
+ /** What the app attached about the thing reported — `useFeedbackSubmit().open(category,
76
+ * { diagnostics })` (keksdose live #481). Sent whatever `attachUrl` says, as the box
77
+ * shows it; left out of the object when it has no key to show. */
78
+ diagnostics?: Readonly<Record<string, unknown>> | null;
73
79
  }
74
80
 
75
81
  /** The pathname of a full or path-only URL; `""` for something that is neither. */
@@ -85,12 +91,13 @@ function pathOf(url: string, base: string | undefined): string {
85
91
  /**
86
92
  * The `context` object of `POST /feedback` (§3.2), keys in keksdose's order: `url`, `route`,
87
93
  * `origin`, `environment`, `user_id`, `user_email`, `user_display_name`, `viewport`, `ua`,
88
- * `version`.
94
+ * `version`, then the app's `diagnostics`.
89
95
  *
90
96
  * With `attachUrl: false`, `url` and `route` are `""` — present and empty, as keksdose
91
97
  * sends them, so a reader can tell "withheld" from "an old row without the key".
92
98
  * `environment` and `version` are omitted when the app passes none (kastlan had no
93
- * version), rather than sent as `undefined`.
99
+ * version), rather than sent as `undefined`; `diagnostics` when the box would show no
100
+ * line of it (none, `{}`, not a record).
94
101
  *
95
102
  * Reads `window` / `navigator` only for the defaults it was not given, so it is a pure
96
103
  * function in a test that passes everything.
@@ -111,6 +118,8 @@ export function feedbackContext(input: FeedbackContextInput): FeedbackContext {
111
118
  context.viewport = input.viewport ?? (win ? `${win.innerWidth}x${win.innerHeight}` : "");
112
119
  context.ua = input.ua ?? (typeof navigator === "undefined" ? "" : navigator.userAgent);
113
120
  if (input.version !== undefined) context.version = input.version;
121
+ // The box's own test: what it draws no line of is not sent either.
122
+ if (input.diagnostics && diagnosticsEntries(input.diagnostics).length) context.diagnostics = input.diagnostics;
114
123
  return context;
115
124
  }
116
125
 
@@ -127,6 +136,9 @@ export interface FeedbackContextLabels {
127
136
  user: string;
128
137
  /** The checkbox. The URL it is about is the line beneath it. */
129
138
  attachUrl: string;
139
+ /** The heading over what the app attached — `open(category, { diagnostics })`, shown
140
+ * read-only under the URL (keksdose live #481). de-CH: "Mitgesendete Diagnose" */
141
+ diagnostics: string;
130
142
  }
131
143
 
132
144
  // Defined in ./feedback-context-labels, which holds no component code (0.34, KS U8).
@@ -140,6 +152,10 @@ export interface FeedbackContextBoxProps {
140
152
  /** The checkbox, on by default in the dialog (§4.2) — the same `attachUrl`. */
141
153
  attachUrl: boolean;
142
154
  onAttachUrlChange: (next: boolean) => void;
155
+ /** What the app attached — the same `diagnostics` handed to {@link feedbackContext}.
156
+ * Shown read-only, each key and its value as the row detail draws them; no block when
157
+ * there is no line to show. */
158
+ diagnostics?: Readonly<Record<string, unknown>> | null;
143
159
  /** Prop > `<UiKitProvider labels={{ feedbackContext }}>` > English. */
144
160
  labels?: LabelOverride<FeedbackContextLabels>;
145
161
  className?: string;
@@ -157,6 +173,10 @@ export interface FeedbackContextBoxProps {
157
173
  * against whatever screen happened to be open, the queue reads as if it were about that
158
174
  * screen.
159
175
  *
176
+ * Below the URL, what the app attached (`diagnostics`, keksdose live #481: a stuck receipt
177
+ * scan's bookkeeping), read-only and in the row detail's own lines: the reporter sees what
178
+ * goes with the report, and that the URL checkbox does not withhold it.
179
+ *
160
180
  * Nothing in here is the app's: hand it the values handed to {@link feedbackContext}, and
161
181
  * what the box shows is what is sent.
162
182
  */
@@ -165,11 +185,14 @@ export function FeedbackContextBox({
165
185
  url,
166
186
  attachUrl,
167
187
  onAttachUrlChange,
188
+ diagnostics,
168
189
  labels: labelsProp,
169
190
  className,
170
191
  }: FeedbackContextBoxProps) {
171
192
  const labels = useKitLabels("feedbackContext", DEFAULT_FEEDBACK_CONTEXT_LABELS, labelsProp);
172
193
  const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
194
+ const diagnosticsId = useId();
195
+ const attached = diagnosticsEntries(diagnostics);
173
196
  const submitter = submitterLine(user);
174
197
  // "User: Ada …" through `common.fieldValue`, so French gets its space before the colon
175
198
  // and Chinese its full-width one; the label and its punctuation are drawn bold, as
@@ -208,6 +231,15 @@ export function FeedbackContextBox({
208
231
  ) : undefined
209
232
  }
210
233
  />
234
+ {attached.length > 0 && (
235
+ // A named group, so a screen reader hears what the pairs are before it reads them.
236
+ <div role="group" aria-labelledby={diagnosticsId} className="space-y-0.5 pt-1">
237
+ <div id={diagnosticsId} className="font-medium text-secondary">
238
+ {labels.diagnostics}
239
+ </div>
240
+ <FeedbackDiagnosticsList entries={attached} />
241
+ </div>
242
+ )}
211
243
  </div>
212
244
  );
213
245
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `context.diagnostics` (§3.2), drawn one way wherever it shows: in the submit dialog's
3
+ * context box, to the reporter before it goes (§4.2), and in the row detail, to the admin
4
+ * and the author afterwards (§4.4.2). One module for both, so the reporter is shown the
5
+ * very lines the admin will read (keksdose live #481).
6
+ *
7
+ * Internal: neither `@eifi1/ui-kit/feedback` nor the barrel names it.
8
+ */
9
+
10
+ /** `diagnostics` as [key, text] pairs, in the app's order: a string, number or boolean as
11
+ * written, an object or a list as JSON, nothing as "—". Empty for no diagnostics, and for
12
+ * a value that is not a plain object — the server stores whatever the client sent. */
13
+ export function diagnosticsEntries(diagnostics: unknown): Array<[string, string]> {
14
+ if (!diagnostics || typeof diagnostics !== "object" || Array.isArray(diagnostics)) return [];
15
+ return Object.entries(diagnostics).map(([key, v]) => [
16
+ key,
17
+ v === null || v === undefined || v === ""
18
+ ? "—"
19
+ : typeof v === "object"
20
+ ? JSON.stringify(v)
21
+ : String(v as string | number | boolean | bigint),
22
+ ]);
23
+ }
24
+
25
+ /** The pairs as a definition list: each key beside its value, in monospace. Read-only in
26
+ * both places; the heading over it is the caller's. */
27
+ export function FeedbackDiagnosticsList({ entries }: { entries: ReadonlyArray<readonly [string, string]> }) {
28
+ return (
29
+ <dl
30
+ className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-3 gap-y-0.5 font-mono text-xs"
31
+ data-feedback-diagnostics=""
32
+ >
33
+ {entries.map(([key, text]) => (
34
+ <div key={key} className="contents">
35
+ <dt className="text-muted">{key}</dt>
36
+ <dd className="min-w-0 break-words text-secondary">{text}</dd>
37
+ </div>
38
+ ))}
39
+ </dl>
40
+ );
41
+ }
@@ -146,6 +146,16 @@ interface FeedbackDialogBaseProps<C extends string = string> {
146
146
  * English. Optional since 0.12.0; a whole `FeedbackDialogLabels` still fits. */
147
147
  labels?: LabelOverride<FeedbackDialogLabels>;
148
148
  submitting?: boolean;
149
+ /**
150
+ * The subject and the body each opening starts with, in place of empty fields — the
151
+ * reporter edits them like anything typed. `useFeedbackSubmit().open(category, { title,
152
+ * body })` passes them (keksdose live #481: "Report this problem" opens on words the
153
+ * screen already knows). Read when the dialog opens; a change while it is open leaves
154
+ * the draft alone.
155
+ */
156
+ defaultTitle?: string;
157
+ /** The body's, as `defaultTitle` is the subject's. */
158
+ defaultBody?: string;
149
159
  contextSlot?: ReactNode;
150
160
  attachmentAccept?: string[];
151
161
  maxAttachmentBytes?: number;
@@ -245,6 +255,8 @@ export function FeedbackDialog<C extends string = string>(props: FeedbackDialogP
245
255
  onCategoryChange,
246
256
  labels: labelsProp,
247
257
  submitting = false,
258
+ defaultTitle = "",
259
+ defaultBody = "",
248
260
  contextSlot,
249
261
  attachmentAccept = DEFAULT_ATTACHMENT_ACCEPT,
250
262
  maxAttachmentBytes = DEFAULT_MAX_ATTACHMENT_BYTES,
@@ -254,8 +266,8 @@ export function FeedbackDialog<C extends string = string>(props: FeedbackDialogP
254
266
  attachmentButtonSize,
255
267
  publicNotice,
256
268
  } = props;
257
- const [title, setTitle] = useState("");
258
- const [body, setBody] = useState("");
269
+ const [title, setTitle] = useState(defaultTitle);
270
+ const [body, setBody] = useState(defaultBody);
259
271
  const [attachment, setAttachment] = useState<File | null>(null);
260
272
  // `attachments="multiple"` only: the capture, apart from the picked/pasted files.
261
273
  const [screenshot, setScreenshot] = useState<File | null>(null);
@@ -283,8 +295,8 @@ export function FeedbackDialog<C extends string = string>(props: FeedbackDialogP
283
295
  if (open !== wasOpen) {
284
296
  setWasOpen(open);
285
297
  if (open) {
286
- setTitle("");
287
- setBody("");
298
+ setTitle(defaultTitle);
299
+ setBody(defaultBody);
288
300
  setAttachment(null);
289
301
  setScreenshot(null);
290
302
  setFiles([]);
@@ -1,41 +1,20 @@
1
- import { isThenable } from "../lib/thenable";
2
-
3
1
  /**
4
- * A page's mutation in either of the forms the feedback parts take, run once (refactor
5
- * audit 2026-10-30, FLW-3).
2
+ * A page's mutation, run once (refactor audit 2026-10-30, FLW-3): TanStack Query's
3
+ * `mutateAsync`, or any `(patch) => Promise` that resolves once the PATCH has landed.
6
4
  *
7
- * The parts used to take TanStack Query's `mutate` with a per-call `onSuccess`. TanStack
8
- * v5 calls per-call callbacks for the LATEST `mutate()` on an observer only — it
9
- * overwrites the options and detaches from the earlier mutation — so a second change sent
10
- * before the first PATCH answered (another swipe, another pill, the Undo itself) lost the
11
- * first change's Undo toast, and the detail's editor stayed open. The promise
12
- * `mutateAsync` returns settles for every call, whatever the observer does next.
5
+ * The parts took TanStack Query's `mutate` with a per-call `onSuccess` until 0.53
6
+ * (deprecated in 0.52). TanStack v5 calls per-call callbacks for the LATEST `mutate()` on
7
+ * an observer only — it overwrites the options and detaches from the earlier mutation —
8
+ * so a second change sent before the first PATCH answered (another swipe, another pill,
9
+ * the Undo itself) lost the first change's Undo toast, and the detail's editor stayed
10
+ * open. The promise `mutateAsync` returns settles for every call, whatever the observer
11
+ * does next.
13
12
  *
14
- * Both forms are called the same way — the patch and `{ onSuccess }` — and the answer is
15
- * taken from whichever comes first: the callback, or the promise the call returned. A
16
- * rejected promise is the mutation's own to report (its `onError`), so it is caught here
13
+ * A rejected promise is the mutation's own to report (its `onError`), so it is caught here
17
14
  * and nothing more happens.
18
15
  *
19
16
  * @internal Not part of `/feedback` or the barrel.
20
17
  */
21
-
22
- /**
23
- * A mutation in either form: the promise form (0.52) — TanStack's `mutateAsync`, any
24
- * `(patch) => Promise` — or the callback form before it — TanStack's `mutate`, a function
25
- * that calls `onSuccess` once the PATCH has landed, and only then. One signature for
26
- * both, so an inline function handed to a prop of this type gets its parameter types (a
27
- * union of the two leaves `async (patch) => …` an implicit `any`).
28
- */
29
- export type FeedbackMutationCall<P> = (patch: P, callbacks: { onSuccess: () => void }) => void | Promise<unknown>;
30
-
31
- /** Send `patch` through `call`, and run `landed` once when it has landed. */
32
- export function runMutation<P>(call: FeedbackMutationCall<P>, patch: P, landed: () => void): void {
33
- let done = false;
34
- const once = () => {
35
- if (done) return;
36
- done = true;
37
- landed();
38
- };
39
- const answer: unknown = call(patch, { onSuccess: once });
40
- if (isThenable(answer)) answer.then(once, () => undefined);
18
+ export function runMutation<P>(call: (patch: P) => Promise<unknown>, patch: P, landed: () => void): void {
19
+ call(patch).then(landed, () => undefined);
41
20
  }
@@ -174,11 +174,6 @@ export interface FeedbackPublicListProps<T extends FeedbackPublicListItem = Feed
174
174
  * what it carries: every other `onOpenChange` in the kit is a boolean (refactor audit
175
175
  * 2026-10-30, API-5). */
176
176
  onOpenIdChange?: (id: number | null) => void;
177
- /**
178
- * @deprecated 0.52 — `onOpenIdChange`, the same call: an `onOpenChange` is a boolean
179
- * everywhere else in the kit; removed in 0.53
180
- */
181
- onOpenChange?: (id: number | null) => void;
182
177
  /**
183
178
  * 0.38.1: the opened report when it is NOT on this page — a `?row=` link to an older
184
179
  * one (Kurvenschmiede). The app fetches it (`GET /feedback/public/{id}`, a list item's
@@ -249,7 +244,6 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
249
244
  loading = false,
250
245
  openId: openIdProp,
251
246
  onOpenIdChange,
252
- onOpenChange,
253
247
  openRow,
254
248
  page: pageIndex = 0,
255
249
  hasMore = false,
@@ -269,7 +263,7 @@ export function FeedbackPublicList<T extends FeedbackPublicListItem = FeedbackPu
269
263
  const openId = openIdProp !== undefined ? openIdProp : ownOpenId;
270
264
  const setOpen = (id: number | null) => {
271
265
  if (openIdProp === undefined) setOwnOpenId(id);
272
- (onOpenIdChange ?? onOpenChange)?.(id);
266
+ onOpenIdChange?.(id);
273
267
  };
274
268
  // A `?row=` report from another page: first, while it is the open one, and only when
275
269
  // the page does not have it itself.
@@ -59,6 +59,11 @@ export interface FeedbackContext {
59
59
  ua?: string;
60
60
  /** The app build (`__APP_VERSION__`). */
61
61
  version?: string;
62
+ /** What the app attached about the thing reported (keksdose's stuck receipt scan, live
63
+ * #481): a flat record of short values, in the order the app wrote them. The row
64
+ * detail shows it to the admin and the author, never to anyone else; erasure drops it
65
+ * (it is not on server-kit's `FEEDBACK_CONTEXT_KEEP`). */
66
+ diagnostics?: Readonly<Record<string, unknown>>;
62
67
  // Crash rows (server-written, keksdose `feedback_service.py:346`).
63
68
  fingerprint?: string;
64
69
  /** Which error boundary caught it. */
@@ -14,20 +14,13 @@ export const DEFAULT_FEEDBACK_DETAIL_LABELS: FeedbackDetailLabels = {
14
14
  cancel: "Cancel",
15
15
  url: "URL",
16
16
  copyUrl: "Copy URL",
17
+ diagnostics: "Diagnostics",
17
18
  attachment: "Attachment",
18
19
  download: (name) => `Download ${name}`,
19
20
  downloadFailed: "The attachment could not be downloaded.",
20
21
  outcome: "Outcome",
21
22
  replies: "Replies",
22
23
  resolvedAt: (date) => `Resolved: ${date}`,
23
- // The outcome editor's three words, read by nothing since 0.38 and "kept for one
24
- // release" for 13 minors: no tag, so the deprecations test never listed them (refactor
25
- // audit 2026-10-30, DOC-9). Deprecated on `FeedbackDetailLabels`, the type an app reads
26
- // them through — a tag on these keys reached no reader (0.52 code review). The keys
27
- // leave the type and all seven catalogues in 0.53.
28
- outcomeAdd: "Add outcome",
29
- outcomeUpdate: "Update",
30
- outcomePlaceholder: "What was done, decided, or why this won't be addressed.",
31
24
  rework: "Rework",
32
25
  reworkTitle: "Send for rework",
33
26
  reworkSend: "Send rework",