@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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 (222) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +12 -4
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/action-form-support.d.ts +1 -0
  7. package/dist/app/action-form-support.js +1 -1
  8. package/dist/app/app-features-context.js +4 -4
  9. package/dist/app/column-renderers.d.ts +7 -9
  10. package/dist/app/column-renderers.js +15 -18
  11. package/dist/app/config-edit-body.js +7 -3
  12. package/dist/app/config-edit-shim.d.ts +7 -8
  13. package/dist/app/config-edit-shim.js +18 -20
  14. package/dist/app/custom-screens.d.ts +3 -3
  15. package/dist/app/custom-screens.js +8 -8
  16. package/dist/app/dashboard-body.d.ts +2 -2
  17. package/dist/app/dashboard-body.js +2 -2
  18. package/dist/app/entity-edit-prefill.d.ts +11 -0
  19. package/dist/app/entity-edit-prefill.js +18 -13
  20. package/dist/app/entity-edit-screen.js +4 -4
  21. package/dist/app/entity-list-screen.js +3 -3
  22. package/dist/app/extension-form-submit.d.ts +1 -1
  23. package/dist/app/extension-form-submit.js +14 -15
  24. package/dist/app/extension-sections.d.ts +29 -28
  25. package/dist/app/extension-sections.js +10 -10
  26. package/dist/app/feature-schema.js +11 -11
  27. package/dist/app/form-schema.js +2 -2
  28. package/dist/app/header-actions-bar.js +2 -2
  29. package/dist/app/kumiko-screen.js +8 -10
  30. package/dist/app/layout-fields.js +1 -1
  31. package/dist/app/list-facets.js +3 -3
  32. package/dist/app/nav.d.ts +10 -10
  33. package/dist/app/nav.js +13 -13
  34. package/dist/app/projection-detail-body.js +4 -4
  35. package/dist/app/projection-detail-shim.d.ts +6 -5
  36. package/dist/app/projection-detail-shim.js +23 -22
  37. package/dist/app/projection-list-body.js +10 -10
  38. package/dist/app/projection-list-shim.d.ts +5 -5
  39. package/dist/app/projection-list-shim.js +17 -18
  40. package/dist/app/qn.js +10 -12
  41. package/dist/app/qualify-screen-id.js +1 -1
  42. package/dist/app/screen-access.js +2 -2
  43. package/dist/app/screen-shared.js +1 -1
  44. package/dist/app/secret-mint-body.js +4 -4
  45. package/dist/app/write-failed-error.js +8 -7
  46. package/dist/components/edit-mounts.d.ts +2 -2
  47. package/dist/components/embedded-list-field.js +5 -5
  48. package/dist/components/grid-cell-for-field.d.ts +1 -1
  49. package/dist/components/reference-create-dialog.js +3 -3
  50. package/dist/components/related-list-section.d.ts +1 -1
  51. package/dist/components/related-list-section.js +15 -2
  52. package/dist/components/render-edit-action-button.d.ts +1 -3
  53. package/dist/components/render-edit-action-button.js +2 -2
  54. package/dist/components/render-edit-logic.js +1 -1
  55. package/dist/components/render-edit-types.d.ts +6 -6
  56. package/dist/components/render-edit.js +74 -73
  57. package/dist/components/render-field.d.ts +7 -7
  58. package/dist/components/render-field.js +62 -61
  59. package/dist/components/render-list.d.ts +42 -43
  60. package/dist/components/render-list.js +93 -84
  61. package/dist/components/use-wizard-form-draft.js +10 -10
  62. package/dist/context/dispatcher-context.js +5 -5
  63. package/dist/context/user-roles-context.js +2 -2
  64. package/dist/flow-diagram/render.js +9 -1
  65. package/dist/headless/apex/index.d.ts +1 -2
  66. package/dist/headless/apex/index.js +5 -6
  67. package/dist/headless/form/form-controller.js +14 -6
  68. package/dist/headless/form/types.d.ts +1 -0
  69. package/dist/headless/form/zod-bridge.js +2 -2
  70. package/dist/headless/format/index.js +89 -64
  71. package/dist/headless/format/money.js +1 -1
  72. package/dist/headless/index.js +2 -2
  73. package/dist/headless/view-model/edit.js +4 -2
  74. package/dist/headless/view-model/list.js +184 -150
  75. package/dist/headless/view-model/types.d.ts +6 -3
  76. package/dist/hooks/live-refetch-scheduler.js +4 -0
  77. package/dist/hooks/reference-limits.js +11 -12
  78. package/dist/hooks/use-list-url-state.d.ts +13 -13
  79. package/dist/hooks/use-list-url-state.js +21 -22
  80. package/dist/hooks/use-query.js +3 -3
  81. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  82. package/dist/hooks/use-reference-lookup.js +32 -19
  83. package/dist/hooks/use-store.js +18 -22
  84. package/dist/hooks/use-stream-handler.js +4 -2
  85. package/dist/i18n-defaults.js +6 -8
  86. package/dist/i18n.d.ts +10 -10
  87. package/dist/i18n.js +25 -25
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +6 -4
  90. package/dist/live/dispatcher-live.js +2 -2
  91. package/dist/primitives.d.ts +244 -221
  92. package/dist/primitives.js +1 -1
  93. package/dist/sse/live-events.d.ts +5 -5
  94. package/dist/sse/live-events.js +17 -17
  95. package/dist/tokens.d.ts +8 -8
  96. package/dist/tokens.js +16 -17
  97. package/dist/web/app/browser-locale.d.ts +9 -9
  98. package/dist/web/app/browser-locale.js +19 -19
  99. package/dist/web/app/client-plugin.d.ts +44 -52
  100. package/dist/web/app/client-plugin.js +4 -5
  101. package/dist/web/app/create-app.d.ts +28 -31
  102. package/dist/web/app/create-app.js +29 -25
  103. package/dist/web/app/dashboard-body.js +17 -1
  104. package/dist/web/app/dashboard-panels.js +12 -2
  105. package/dist/web/app/document-lang-sync.js +1 -1
  106. package/dist/web/app/draft-storage.js +1 -1
  107. package/dist/web/app/nav-providers-context.d.ts +3 -3
  108. package/dist/web/app/nav-providers-context.js +3 -3
  109. package/dist/web/app/nav.d.ts +18 -17
  110. package/dist/web/app/nav.js +66 -64
  111. package/dist/web/app/rich-content-editor.js +1 -1
  112. package/dist/web/components/config-cascade.js +17 -18
  113. package/dist/web/components/config-source-badge.js +4 -4
  114. package/dist/web/index.d.ts +1 -1
  115. package/dist/web/index.js +1 -1
  116. package/dist/web/layout/app-layout.d.ts +9 -9
  117. package/dist/web/layout/app-layout.js +2 -2
  118. package/dist/web/layout/avatar.d.ts +4 -3
  119. package/dist/web/layout/avatar.js +15 -17
  120. package/dist/web/layout/default-app-shell.d.ts +22 -23
  121. package/dist/web/layout/editor-panel.js +3 -3
  122. package/dist/web/layout/nav-tree.js +86 -84
  123. package/dist/web/layout/profile-menu.d.ts +1 -1
  124. package/dist/web/layout/shell-header.js +2 -1
  125. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  126. package/dist/web/layout/sidebar-user.d.ts +1 -1
  127. package/dist/web/layout/sidebar-user.js +4 -4
  128. package/dist/web/layout/sidebar.d.ts +8 -8
  129. package/dist/web/layout/sidebar.js +7 -7
  130. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  131. package/dist/web/layout/target-resolver-stub.js +20 -20
  132. package/dist/web/layout/target-url.d.ts +7 -7
  133. package/dist/web/layout/target-url.js +22 -22
  134. package/dist/web/layout/theme-toggle.js +4 -5
  135. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  136. package/dist/web/layout/uniform-bar-controls.js +4 -0
  137. package/dist/web/layout/workspace-shell.d.ts +21 -21
  138. package/dist/web/layout/workspace-shell.js +29 -30
  139. package/dist/web/lib/cn.d.ts +4 -4
  140. package/dist/web/lib/cn.js +4 -4
  141. package/dist/web/lib/file-ref-download.js +4 -1
  142. package/dist/web/primitives/action-menu.d.ts +8 -8
  143. package/dist/web/primitives/action-menu.js +14 -13
  144. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  145. package/dist/web/primitives/calendar-popover.js +10 -10
  146. package/dist/web/primitives/card-styles.js +1 -1
  147. package/dist/web/primitives/card.js +2 -2
  148. package/dist/web/primitives/code-input.d.ts +6 -0
  149. package/dist/web/primitives/code-input.js +31 -0
  150. package/dist/web/primitives/combobox.d.ts +12 -12
  151. package/dist/web/primitives/combobox.js +34 -35
  152. package/dist/web/primitives/data-table-cell.js +2 -2
  153. package/dist/web/primitives/data-table.js +6 -6
  154. package/dist/web/primitives/date-field.js +2 -2
  155. package/dist/web/primitives/date-input.js +4 -4
  156. package/dist/web/primitives/default-form.js +2 -2
  157. package/dist/web/primitives/dropdown-menu.js +14 -15
  158. package/dist/web/primitives/embedded-list-input.js +3 -3
  159. package/dist/web/primitives/facet-filter.js +2 -2
  160. package/dist/web/primitives/field.js +9 -8
  161. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  162. package/dist/web/primitives/file-thumbnail.js +5 -0
  163. package/dist/web/primitives/form.d.ts +1 -1
  164. package/dist/web/primitives/form.js +6 -6
  165. package/dist/web/primitives/input.js +8 -3
  166. package/dist/web/primitives/layout-elements.js +7 -5
  167. package/dist/web/primitives/layout.js +2 -2
  168. package/dist/web/primitives/located-timestamp-input.js +4 -4
  169. package/dist/web/primitives/money-input.js +13 -17
  170. package/dist/web/primitives/page-header.js +3 -1
  171. package/dist/web/primitives/pager.js +1 -1
  172. package/dist/web/primitives/row-actions.js +6 -7
  173. package/dist/web/primitives/secret-reveal.js +1 -1
  174. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  175. package/dist/web/primitives/timestamp-input.js +21 -21
  176. package/dist/web/primitives/toast.js +12 -13
  177. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  178. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  179. package/dist/web/sse/live-events.d.ts +6 -6
  180. package/dist/web/sse/live-events.js +3 -3
  181. package/dist/web/tokens.d.ts +11 -12
  182. package/dist/web/tokens.js +35 -37
  183. package/dist/web/version/update-checker.js +14 -13
  184. package/dist/web/widgets/ai-text-field.js +1 -1
  185. package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
  186. package/dist/web/widgets/bounded-load-cache.js +10 -4
  187. package/dist/web/widgets/charts.d.ts +10 -10
  188. package/dist/web/widgets/charts.js +8 -8
  189. package/dist/web/widgets/choice-cards.d.ts +17 -0
  190. package/dist/web/widgets/choice-cards.js +12 -0
  191. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  192. package/dist/web/widgets/collapsible-section.js +7 -7
  193. package/dist/web/widgets/detail-list.d.ts +5 -3
  194. package/dist/web/widgets/detail-list.js +5 -3
  195. package/dist/web/widgets/feed-list.d.ts +2 -2
  196. package/dist/web/widgets/feed-list.js +2 -2
  197. package/dist/web/widgets/form-fields.d.ts +14 -14
  198. package/dist/web/widgets/form-fields.js +14 -13
  199. package/dist/web/widgets/index.d.ts +1 -0
  200. package/dist/web/widgets/index.js +5 -4
  201. package/dist/web/widgets/infinity-list.js +3 -3
  202. package/dist/web/widgets/mode-switch.d.ts +2 -2
  203. package/dist/web/widgets/mode-switch.js +2 -2
  204. package/dist/web/widgets/pdf-preview.js +1 -1
  205. package/dist/web/widgets/plan-card.d.ts +2 -0
  206. package/dist/web/widgets/plan-card.js +1 -1
  207. package/dist/web/widgets/progress-list.d.ts +1 -2
  208. package/dist/web/widgets/progress-list.js +1 -2
  209. package/dist/web/widgets/query-table.d.ts +6 -6
  210. package/dist/web/widgets/result-panel.d.ts +16 -16
  211. package/dist/web/widgets/result-panel.js +10 -10
  212. package/dist/web/widgets/section-card.d.ts +3 -3
  213. package/dist/web/widgets/section-card.js +3 -3
  214. package/dist/web/widgets/stat.d.ts +4 -4
  215. package/dist/web/widgets/stat.js +6 -6
  216. package/dist/web/widgets/states.d.ts +4 -4
  217. package/dist/web/widgets/states.js +4 -4
  218. package/dist/web/widgets/upload-zone.js +13 -5
  219. package/dist/web/widgets/use-draft.d.ts +7 -7
  220. package/dist/web/widgets/use-draft.js +7 -7
  221. package/package.json +6 -6
  222. package/src/changes.json +49 -0
@@ -1,21 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // MoneyInput — type=text mit focus-aware Locale-Format. Canonical-Wert
3
- // bleiben Minor-Units (Cents) wie auf der Wire; während Focus zeigt das
4
- // Input einen rohen, editierbaren Decimal-String. Bei Blur formatiert
5
- // Intl.NumberFormat mit `style: "currency"` — das liefert Currency-
6
- // Symbol (€/$/¥) UND Tausender-Trenner UND korrekte Decimals in einem
7
- // Aufruf.
2
+ // MoneyInput uses locale-aware formatting while preserving minor units (cents) as the canonical value.
3
+ // On focus, it shows a raw editable decimal string; on blur, Intl.NumberFormat with
4
+ // `style: "currency"` supplies the currency symbol (€/$/¥), grouping separators, and
5
+ // correct decimal places in one call.
8
6
  //
9
- // Warum nicht type=number: number-Inputs lehnen formatierte Strings
10
- // ("1.234,56 €") ab — kein Browser akzeptiert Locale-Decimals (Komma)
11
- // in number-Inputs. inputMode="decimal" gibt mobiles Numpad-Keyboard
12
- // trotzdem.
7
+ // `type=number` cannot accept formatted strings such as ("1.234,56 €") or locale-specific
8
+ // decimal separators such as commas. `inputMode="decimal"` still provides a mobile numeric
9
+ // keypad.
13
10
  import { useLayoutEffect, useMemo, useRef, useState } from "react";
14
11
  import { currencyDecimals, formatMoney as formatMoneyForLocale } from "../../headless/index.js";
15
12
  import { cn } from "../lib/cn.js";
16
13
  import { parseLocaleNumber, resolveSafeLocale } from "./locale-number.js";
17
14
  // Re-exported for backward compat — callers used to import this from here
18
- // before it moved to headless (shared with RenderField, kumiko-framework#1923).
15
+ // before it moved to headless (shared with RenderField).
19
16
  export { currencyDecimals, parseLocaleNumber };
20
17
  // The border and focus ring live on the wrapper so the currency symbol can be
21
18
  // a flex sibling of the input: a symbol of any width ("€", "CHF", "R$") takes
@@ -24,22 +21,21 @@ const wrapperClass = "flex h-9 w-full items-center rounded-md border border-inpu
24
21
  "transition-colors focus-within:ring-1 focus-within:ring-ring";
25
22
  const inputClass = "h-full min-w-0 flex-1 bg-transparent px-3 placeholder:text-muted-foreground " +
26
23
  "focus-visible:outline-none disabled:cursor-not-allowed " +
27
- // Numerische Inputs rechtsbündig — wie native type=number — damit
28
- // Beträge unter Listen-Spalten an den Tausender-Stellen alignen.
24
+ // Right-aligns the value like a native type=number input so amounts align at the thousands
25
+ // separators in list columns.
29
26
  "text-right tabular-nums";
30
27
  export function MoneyInput({ id, name, value, onChange, currency, locale, disabled, required, hasError, ariaLabel, }) {
31
28
  const decimals = currencyDecimals(currency);
32
29
  const factor = 10 ** decimals;
33
30
  const resolvedLocale = resolveSafeLocale(locale);
34
31
  const [focused, setFocused] = useState(false);
35
- // Raw-Edit-Buffer während Focus. Sonst würde jeder Tipp-Step durch
36
- // Math.round → format-Roundtrip jagen und der Cursor würde springen.
32
+ // Keeps edits raw while focused; otherwise each keystroke would round-trip through
33
+ // Math.round and formatting, causing the cursor to jump.
37
34
  const [draft, setDraft] = useState("");
38
35
  const inputRef = useRef(null);
39
36
  const major = value === "" ? null : value / factor;
40
37
  const { symbol, symbolPosition, formatNumber } = useMemo(() => moneyFormatParts(currency, resolvedLocale), [currency, resolvedLocale]);
41
38
  const formatted = value === "" ? "" : formatNumber(value / factor);
42
- // Edit-Mode: Decimal-String ohne Tausender-Trenner.
43
39
  const toEditable = (m) => m === null
44
40
  ? ""
45
41
  : m.toLocaleString(resolvedLocale, {
@@ -58,7 +54,7 @@ export function MoneyInput({ id, name, value, onChange, currency, locale, disabl
58
54
  // to the *end* of the new value rather than preserving position — so
59
55
  // without an explicit re-select, typing right after focus appends
60
56
  // instead of replacing. That's what silently corrupted values set via
61
- // Playwright's `.fill()` (framework#1856). It also matches standard
57
+ // Playwright's `.fill()`. It also matches standard
62
58
  // money-input UX (immediate overtype on click), and sidesteps mapping a
63
59
  // click position from the formatted view to the editable one, which has
64
60
  // no well-defined equivalent once separators and the currency symbol
@@ -2,6 +2,8 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { useEffect } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { usePageHeaderSlot } from "../layout/page-header-slot.js";
5
+ import { UNIFORM_BAR_CONTROLS_CLASSES } from "../layout/uniform-bar-controls.js";
6
+ import { cn } from "../lib/cn.js";
5
7
  export function DefaultPageHeader({ title, recordTitle, status, actions, overflowItems, hideBreadcrumb, breadcrumbScreenId, }) {
6
8
  const slot = usePageHeaderSlot();
7
9
  const setOverflowItems = slot?.setOverflowItems;
@@ -42,7 +44,7 @@ export function DefaultPageHeader({ title, recordTitle, status, actions, overflo
42
44
  return () => setOverflowItems(undefined);
43
45
  }, [setOverflowItems, overflowItems]);
44
46
  if (slot === null) {
45
- return (_jsxs("div", { "data-kumiko-layout": "page-header-inline", className: "flex items-center gap-2 px-4 py-2", children: [status, _jsx("div", { className: "ml-auto flex items-center gap-2", children: actions })] }));
47
+ return (_jsxs("div", { "data-kumiko-layout": "page-header-inline", className: "flex items-center gap-2 px-4 py-2", children: [status, _jsx("div", { className: cn("ml-auto flex items-center gap-2", UNIFORM_BAR_CONTROLS_CLASSES), children: actions })] }));
46
48
  }
47
49
  return (_jsxs(_Fragment, { children: [status !== undefined &&
48
50
  slot.statusElement !== null &&
@@ -20,7 +20,7 @@ export const TABLE_FOOTER_BAR_CLASS = cn("flex h-15 shrink-0 items-center gap-4
20
20
  // `screen.pageSize ?? 50`) rather than the real per-screen pageSize, which isn't
21
21
  // threaded down to this component. A screen with a custom pageSize can still
22
22
  // under- or over-show the marker; thread pageSize through KumikoScreen →
23
- // RenderList → DataTable → InfiniteSentinel if that's needed (fw#1713).
23
+ // RenderList → DataTable → InfiniteSentinel if that's needed.
24
24
  export const END_LABEL_MIN_ROWS = 50;
25
25
  export function InfiniteSentinel({ onReachEnd, loadingMore, hasMore, rowsCount, testId, }) {
26
26
  const t = useTranslation();
@@ -10,9 +10,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
10
10
  import { useToast } from "./toast.js";
11
11
  import "../react-css-custom-properties.js";
12
12
  import { actionIconFor, SECONDARY_BUTTON_EXTRA_CLASSES } from "./button.js";
13
- // The primary row action is the one that always stays a visible text
14
- // button — `edit` if declared, else the first visible action (fw
15
- // bedienkonzept L3: "Bearbeiten steht in jeder Zeile, immer, als Text").
13
+ // The primary row action always remains a visible text button: `edit` when declared,
14
+ // otherwise the first visible action ("Edit appears in every row, always, as text").
16
15
  function primaryRowAction(actions) {
17
16
  return actions.find((a) => a.id === "edit") ?? actions[0];
18
17
  }
@@ -38,19 +37,19 @@ export function RowActionsCell({ row, actions, mode = "adaptive", rowIsLink = fa
38
37
  // otherwise "inline" is exactly the wall-to-wall text-button problem
39
38
  // this feature exists to fix. An author-set `display` takes that choice over.
40
39
  const iconOnly = !hasExplicitDisplay && shouldRenderActionsIconOnly(visible);
41
- return (_jsx("div", { className: "flex w-full items-center gap-1 justify-start", children: visible.map((a) => (_jsx(RowActionButton, { row: row, action: a, iconOnly: iconOnly, compact: compact }, a.id))) }));
40
+ return (_jsx("div", { className: "flex w-full flex-nowrap items-center gap-1 justify-start whitespace-nowrap", children: visible.map((a) => (_jsx(RowActionButton, { row: row, action: a, iconOnly: iconOnly, compact: compact }, a.id))) }));
42
41
  }
43
42
  if (hasExplicitDisplay) {
44
43
  const inline = visible.filter((a) => a.display !== undefined);
45
44
  const rest = visible.filter((a) => a.display === undefined);
46
- return (_jsxs("div", { className: "inline-flex flex-wrap items-center gap-1 justify-end", children: [inline.map((a) => (_jsx(RowActionButton, { row: row, action: a, compact: compact }, a.id))), rest.length > 0 && _jsx(RowActionsKebab, { row: row, actions: rest })] }));
45
+ return (_jsxs("div", { className: "inline-flex flex-nowrap items-center gap-1 justify-end whitespace-nowrap", children: [inline.map((a) => (_jsx(RowActionButton, { row: row, action: a, compact: compact }, a.id))), rest.length > 0 && _jsx(RowActionsKebab, { row: row, actions: rest })] }));
47
46
  }
48
47
  if (rowIsLink) {
49
48
  return (_jsx("div", { className: "inline-flex items-center justify-end", children: _jsx(RowActionsKebab, { row: row, actions: visible }) }));
50
49
  }
51
50
  const primary = primaryRowAction(visible);
52
51
  const rest = visible.filter((a) => a.id !== primary?.id);
53
- return (_jsxs("div", { className: "inline-flex flex-wrap items-center gap-1 justify-end", children: [primary !== undefined && _jsx(RowActionButton, { row: row, action: primary, asLink: true }), rest.length > 0 && _jsx(RowActionsKebab, { row: row, actions: rest })] }));
52
+ return (_jsxs("div", { className: "inline-flex flex-nowrap items-center gap-1 justify-end whitespace-nowrap", children: [primary !== undefined && _jsx(RowActionButton, { row: row, action: primary, asLink: true }), rest.length > 0 && _jsx(RowActionsKebab, { row: row, actions: rest })] }));
54
53
  }
55
54
  // Shared trigger state between inline button + kebab item: busy flag
56
55
  // (while the async onTrigger runs) + confirm-pending action. Both
@@ -161,7 +160,7 @@ function RowActionsKebab({ row, actions, }) {
161
160
  await triggerNow(action);
162
161
  }, testId: `row-${row.id}-action-${pendingConfirm.id}-dialog` }))] }));
163
162
  }
164
- // Header/row-actions overflow menu (A7): the trigger carries the classes of a
163
+ // Header/row-actions overflow menu: the trigger carries the classes of a
165
164
  // secondary icon Button so it matches neighbouring icon buttons, including
166
165
  // the 44 px touch size on phones. Generic ActionMenuItemSpec items instead of
167
166
  // the DataTableRowAction schema (callers own confirm/danger handling per item).
@@ -42,7 +42,7 @@ function QrSecretValue({ value }) {
42
42
  // biome-ignore lint/security/noDangerouslySetInnerHtml: qrcode-generated SVG, no user input
43
43
  dangerouslySetInnerHTML: { __html: svg } }));
44
44
  }
45
- // One-time secret reveal (fw#2548, secretMint confirm phase) — monospaced
45
+ // One-time secret reveal (secretMint confirm phase) — monospaced
46
46
  // per-value display with a copy button that flips to `copiedLabel` on
47
47
  // success. Silent-catch on clipboard error mirrors the copy-link pattern in
48
48
  // create-app.tsx.
@@ -1,11 +1,11 @@
1
1
  import { type ReactNode } from "react";
2
- /** Form-State → datetime-local-Format (`yyyy-MM-ddTHH:mm`).
3
- * UTC-Instants (mit `Z`/Offset) werden in lokale Wall-Clock
4
- * umgerechnet; offset-lose Werte nur auf Minuten gekürzt. */
2
+ /** Form state → datetime-local format (`yyyy-MM-ddTHH:mm`).
3
+ * UTC instants (with `Z`/offset) are converted to the local wall clock;
4
+ * offset-free values are only truncated to minutes. */
5
5
  export declare function timestampToInputValue(value: string): string;
6
- /** datetime-local-Wert → Wire-Format. wallClock=true reicht die
7
- * Wall-Clock unverändert durch; sonst wird die lokale Zeit als
8
- * UTC-Instant mit `Z`-Suffix emittiert. */
6
+ /** datetime-local value → wire format. wallClock=true passes the wall clock
7
+ * through unchanged; otherwise the local time is emitted as a UTC instant
8
+ * with a `Z` suffix. */
9
9
  export declare function inputValueToTimestamp(raw: string, wallClock: boolean): string | undefined;
10
10
  export type TimestampInputProps = {
11
11
  readonly id: string;
@@ -18,9 +18,9 @@ export type TimestampInputProps = {
18
18
  readonly hasError?: boolean;
19
19
  readonly ariaLabel?: string;
20
20
  readonly locale?: string;
21
- /** Untere/obere Grenze als ISO-Datetime. Begrenzt den Kalender auf
22
- * Tages-Granularität; die exakte Uhrzeit-Grenze setzt die Zod-Validierung
23
- * durch. */
21
+ /** Lower and upper bounds as ISO datetimes. The calendar is constrained to
22
+ * day-level granularity; Zod validation enforces the exact time bounds.
23
+ */
24
24
  readonly min?: string;
25
25
  readonly max?: string;
26
26
  };
@@ -1,15 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // TimestampInput (kind:"timestamp") — gemeinsame Datums-Eingabe (DateField,
3
- // Tippen + Jahres-Kalender) plus ein Uhrzeit-Input. Bis #369 war dies ein
4
- // natives <input type="datetime-local"> — tippbar, aber mit nicht-stylebarem
5
- // Browser-Picker ohne Jahres-Navigation und divergent zu DateInput.
2
+ // TimestampInput (kind:"timestamp") — shared date input (DateField,
3
+ // typing + year calendar) plus a time input. This replaced the native
4
+ // <input type="datetime-local">: it was typeable, but had an unstyleable
5
+ // browser picker without year navigation and diverged from DateInput.
6
6
  //
7
- // Die Wire-Konvertierung bleibt unverändert: der Server validiert timestamp
8
- // als ISO-UTC mit `Z` (z.iso.datetime()) bzw. als Wall-Clock ohne Offset bei
9
- // locatedTimestamps (z.iso.datetime({ local: true })). Intern rechnen wir
10
- // über die datetime-local-Form `yyyy-MM-ddTHH:mm`; timestampToInputValue /
11
- // inputValueToTimestamp kapseln die UTC↔Wall-Clock-Umrechnung (Bug-Bash-2,
12
- // 2026-06-08) und sind separat getestet.
7
+ // Wire conversion remains unchanged: the server validates timestamp values
8
+ // as ISO UTC with `Z` (z.iso.datetime()) or as an offset-free wall clock for
9
+ // locatedTimestamps (z.iso.datetime({ local: true })). Internally, the
10
+ // datetime-local form `yyyy-MM-ddTHH:mm` is used; timestampToInputValue /
11
+ // inputValueToTimestamp encapsulate the UTC↔wall-clock conversion and are
12
+ // tested separately.
13
13
  import { useState } from "react";
14
14
  import { useTranslation } from "../../index.js";
15
15
  import { cn } from "../lib/cn.js";
@@ -19,9 +19,9 @@ const HAS_OFFSET = /(?:Z|[+-]\d{2}:\d{2})$/;
19
19
  function pad(n) {
20
20
  return String(n).padStart(2, "0");
21
21
  }
22
- /** Form-State → datetime-local-Format (`yyyy-MM-ddTHH:mm`).
23
- * UTC-Instants (mit `Z`/Offset) werden in lokale Wall-Clock
24
- * umgerechnet; offset-lose Werte nur auf Minuten gekürzt. */
22
+ /** Form state → datetime-local format (`yyyy-MM-ddTHH:mm`).
23
+ * UTC instants (with `Z`/offset) are converted to the local wall clock;
24
+ * offset-free values are only truncated to minutes. */
25
25
  export function timestampToInputValue(value) {
26
26
  if (value === "")
27
27
  return "";
@@ -34,16 +34,16 @@ export function timestampToInputValue(value) {
34
34
  const m = LOCAL_MINUTES.exec(value);
35
35
  return m !== null ? m[0] : "";
36
36
  }
37
- /** datetime-local-Wert → Wire-Format. wallClock=true reicht die
38
- * Wall-Clock unverändert durch; sonst wird die lokale Zeit als
39
- * UTC-Instant mit `Z`-Suffix emittiert. */
37
+ /** datetime-local value → wire format. wallClock=true passes the wall clock
38
+ * through unchanged; otherwise the local time is emitted as a UTC instant
39
+ * with a `Z` suffix. */
40
40
  export function inputValueToTimestamp(raw, wallClock) {
41
41
  if (raw === "")
42
42
  return undefined;
43
43
  if (wallClock)
44
44
  return raw;
45
- // Offset-loser Datetime-String wird von Date als LOKALE Zeit geparst
46
- // (ES2020+) — genau die Semantik die datetime-local liefert.
45
+ // An offset-free datetime string is parsed by Date as LOCAL time
46
+ // (ES2020+), matching the semantics provided by datetime-local.
47
47
  const d = new Date(raw);
48
48
  if (Number.isNaN(d.getTime()))
49
49
  return undefined;
@@ -64,9 +64,9 @@ export function TimestampInput({ id, name, value, onChange, wallClock, disabled,
64
64
  const t = useTranslation();
65
65
  const local = timestampToInputValue(value);
66
66
  const isoDate = local !== "" ? local.slice(0, 10) : "";
67
- // Uhrzeit ohne gesetztes Datum würde im Wire-Wert verschwinden — lokal
68
- // halten, bis ein Datum gewählt ist. Sobald `value` ein Datum trägt, ist
69
- // dessen Uhrzeit maßgeblich.
67
+ // A time without a selected date would be lost from the wire value, so keep
68
+ // it locally until a date is selected. Once `value` contains a date, its
69
+ // time takes precedence.
70
70
  const [timeText, setTimeText] = useState("");
71
71
  const effectiveTime = local !== "" ? local.slice(11, 16) : timeText;
72
72
  function emit(nextIsoDate, nextTime) {
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Toast-Primitive auf @radix-ui/react-toast. Ein globaler Provider
3
- // + ein useToast()-Hook der von überall in der App aus Toasts auslösen
4
- // kann. Kein Context-Boilerplate für Konsumenten — der Hook returned
5
- // einen `toast()`-Trigger, mehr Public-API gibt's nicht.
2
+ // Toast primitive built on @radix-ui/react-toast: a global provider and
3
+ // a useToast() hook for triggering toasts throughout the app. Consumers
4
+ // get only the `toast()` trigger, without Context boilerplate.
6
5
  //
7
- // Variant = StatusTone (dieselbe Farb-Familie wie StatusBadge), Default
8
- // "muted" (neutral). Auto-dismiss nach 5s, manuell schließbar via X-Button.
9
- // Der ARIA-Live-Region-Setup kommt komplett aus Radix.
6
+ // Variant is StatusTone, using the same color family as StatusBadge;
7
+ // the default is "muted" (neutral). Toasts auto-dismiss after 5s and can
8
+ // be closed manually with the X button. Radix provides the ARIA live region.
10
9
  import * as Primitive from "@radix-ui/react-toast";
11
10
  import { X } from "lucide-react";
12
11
  import { createContext, useCallback, useContext, useId, useMemo, useRef, useState, } from "react";
@@ -17,18 +16,18 @@ const ToastContext = createContext(null);
17
16
  export function useToast() {
18
17
  const ctx = useContext(ToastContext);
19
18
  if (ctx === null) {
20
- // Nicht throwen — Tests die einen Component ohne Provider rendern
21
- // sollen nicht crashen. No-op statt fehlerhaftes Mount.
19
+ // Do not throw: tests rendering a component without a provider must not
20
+ // crash. Use a no-op instead of failing the mount.
22
21
  return { toast: () => undefined };
23
22
  }
24
23
  return ctx;
25
24
  }
26
25
  export function ToastProvider({ children }) {
27
26
  const [entries, setEntries] = useState([]);
28
- // Monoton wachsender Counter via useRef — useState hätte einen
29
- // closure-Bug: zwei toast()-Calls im selben Tick lesen denselben
30
- // alten Wert aus dem Closure und kollidieren bei der ID. Ref ist
31
- // synchron-mutabel, jeder Aufruf bekommt eine garantiert neue ID.
27
+ // A monotonically increasing useRef counter avoids a closure bug: two
28
+ // toast() calls in the same tick would read the same stale useState value
29
+ // and collide on the ID. The ref is synchronously mutable, so each call
30
+ // receives a guaranteed-unique ID.
32
31
  const idPrefix = useId();
33
32
  const counterRef = useRef(0);
34
33
  const toast = useCallback((opts) => {
@@ -0,0 +1,2 @@
1
+ import { type RefObject } from "react";
2
+ export declare function useFocusOnMount(enabled: boolean | undefined): RefObject<HTMLInputElement | null>;
@@ -0,0 +1,9 @@
1
+ import { useEffect, useRef } from "react";
2
+ export function useFocusOnMount(enabled) {
3
+ const ref = useRef(null);
4
+ useEffect(() => {
5
+ if (enabled === true)
6
+ ref.current?.focus();
7
+ }, [enabled]);
8
+ return ref;
9
+ }
@@ -1,8 +1,8 @@
1
1
  import { type LiveEventSubscriber } from "../../index.ts";
2
2
  export type CreateEventSourceLiveEventsOptions = {
3
- /** URL des SSE-Endpoints. Default: /api/sse (das ist wo
4
- * createSseRoute im Kumiko-Server mountet). Override wenn der
5
- * Mountpath divergiert. */
3
+ /** URL of the SSE endpoint. Defaults to /api/sse, where
4
+ * createSseRoute mounts it on the Kumiko server. Override this when
5
+ * the mount path differs. */
6
6
  readonly url?: string;
7
7
  /** Fires once per connection when the session ended: the server refused the SSE
8
8
  * handshake, or opened the stream with a `session-ended` frame (stale auth
@@ -22,7 +22,7 @@ export type LiveEventTimers = {
22
22
  export declare const RECONNECT_BASE_DELAY_MS = 1000;
23
23
  export declare const RECONNECT_MAX_DELAY_MS = 30000;
24
24
  export declare function reconnectDelayMs(attempt: number, random: number): number;
25
- /** Liefert einen `LiveEventSubscriber` der EventSource-backed ist.
26
- * Normalerweise einmal im App-Bootstrap gerufen und als value an
27
- * `<LiveEventsProvider>` durchgereicht — createKumikoApp tut das. */
25
+ /** Returns an EventSource-backed `LiveEventSubscriber`.
26
+ * Typically called once during app bootstrap and passed as a value to
27
+ * `<LiveEventsProvider>`; `createKumikoApp` does this. */
28
28
  export declare function createEventSourceLiveEvents(options?: CreateEventSourceLiveEventsOptions): LiveEventSubscriber;
@@ -61,9 +61,9 @@ async function isSessionEndedRejection(url) {
61
61
  controller.abort();
62
62
  }
63
63
  }
64
- /** Liefert einen `LiveEventSubscriber` der EventSource-backed ist.
65
- * Normalerweise einmal im App-Bootstrap gerufen und als value an
66
- * `<LiveEventsProvider>` durchgereicht — createKumikoApp tut das. */
64
+ /** Returns an EventSource-backed `LiveEventSubscriber`.
65
+ * Typically called once during app bootstrap and passed as a value to
66
+ * `<LiveEventsProvider>`; `createKumikoApp` does this. */
67
67
  export function createEventSourceLiveEvents(options = {}) {
68
68
  const url = options.url ?? "/api/sse";
69
69
  const timers = options.timers ?? defaultTimers;
@@ -5,20 +5,19 @@ export declare const THEME_STORAGE_KEY = "kumiko:theme";
5
5
  * the host HTML (see header comment) does the same synchronously before the
6
6
  * first paint. */
7
7
  export declare function applyStoredThemeMode(): void;
8
- /** Nur für Tests: der once-per-page-load-Guard ist ein Module-Singleton —
9
- * ohne Reset wäre der Mount-Restore-Pfad nach der ersten Render im
10
- * Testfile strukturell unerreichbar. */
8
+ /** Tests only: the once-per-page-load guard is a module singleton; without a reset,
9
+ * the mount-restore path would be structurally unreachable after the first render
10
+ * in the test file. */
11
11
  export declare function __resetStoredModeAppliedForTests(): void;
12
- /** Hook der eine TokensApi für den Browser baut. Wird von
13
- * createKumikoApp genutzt; App-Code der einen eigenen Token-State
14
- * braucht (z.B. User-Präferenz aus localStorage) kann selber
15
- * `<TokensProvider value={...}>` mounten. */
12
+ /** Builds a TokensApi for the browser. Used by createKumikoApp; app code that needs
13
+ * its own token state (for example, a user preference from localStorage) can mount
14
+ * its own `<TokensProvider value={...}>`. */
16
15
  export declare function useBrowserTokensApi(): TokensApi;
17
- /** Default-Tokens — identisch zu `cssVarTokens` (var-string-refs).
18
- * Light- und Dark-Werte switchen via `.dark`-class auf <html>. */
16
+ /** Default tokens—identical to `cssVarTokens` (variable-string references).
17
+ * Light and dark values switch via the `.dark` class on <html>. */
19
18
  export declare const defaultTokens: Tokens;
20
19
  export declare const lightTokens: Tokens;
21
- /** Historisch: schrieb Tokens auf :root als CSS-vars. Jetzt no-op —
22
- * die CSS-vars leben in styles.css, nicht in JS. Bleibt als Export
23
- * damit alter App-Code nicht bricht. */
20
+ /** Historically wrote tokens to :root as CSS variables. Now a no-op—the CSS
21
+ * variables live in styles.css, not JS. Retained as an export so existing app code
22
+ * does not break. */
24
23
  export declare function applyTokensToCssVars(_tokens: Tokens): void;
@@ -2,26 +2,25 @@ import { useEffect, useState, useSyncExternalStore } from "react";
2
2
  import { createStore } from "../headless/index.js";
3
3
  import { cssVarTokens, } from "../index.js";
4
4
  import { DEFAULT_COLOR_SCHEME_VARIABLE } from "./default-color-scheme.js";
5
- // Web-spezifische TokensApi-Impl. Theme-Toggle via `.dark`-Class auf
6
- // <html>. Die echten Farben leben in styles.css; hier ist nur die
7
- // JS-Seite die den class-switch triggert und React-Consumer mit
8
- // useSyncExternalStore darüber informiert.
5
+ // Web-specific TokensApi implementation. The theme toggle uses the `.dark` class on
6
+ // <html>. The actual colors live in styles.css; this is only the JS side that triggers
7
+ // the class switch and informs React consumers through useSyncExternalStore.
9
8
  //
10
- // Source-of-truth ist der DOM (`<html class="dark">`); der Store ist
11
- // reiner Notification-Bus (Tick-Counter), den setMode/toggleMode bei
12
- // jedem Class-Wechsel hochzählen. So bleibt die DOM-Klasse die einzige
13
- // Wahrheit — readCurrentMode liest sie frisch bei jedem getSnapshot.
9
+ // The DOM (`<html class="dark">`) is the source of truth; the store is only a
10
+ // notification bus (a tick counter) incremented by setMode/toggleMode on every class
11
+ // change. This keeps the DOM class as the sole authority—readCurrentMode reads it
12
+ // freshly on every getSnapshot.
14
13
  //
15
- // Persistence: the choice is stored in localStorage (THEME_STORAGE_KEY) and
16
- // restored on the first hook mount; without it the toggle was lost on every
17
- // reload (prod bug 2026-06-07).
18
- // Preference: "auto" follows prefers-color-scheme live (matchMedia listener),
19
- // "light"/"dark" are explicit choices and win. Without a stored choice,
20
- // AppTheme.defaultColorScheme (CSS variable) applies, otherwise the HTML state.
14
+ // Persistence: the choice is stored in localStorage (THEME_STORAGE_KEY) and restored
15
+ // on the first hook mount; without it, the toggle would be lost on every reload.
16
+ // Preference: "auto" follows prefers-color-scheme live (matchMedia listener), while
17
+ // "light"/"dark" are explicit choices and take precedence. Without a stored choice,
18
+ // AppTheme.defaultColorScheme (CSS variable) applies; otherwise, the HTML state.
21
19
  //
22
- // Against FOUC the host HTML also needs a synchronous inline script BEFORE
23
- // the stylesheet link. Under a strict CSP its hash is the sha256 in the build's
24
- // csp-hashes.json, which runProdApp also hands to hostDispatch as deps.cspScriptHashes:
20
+ // To prevent FOUC, the host HTML also needs a synchronous inline script BEFORE the
21
+ // stylesheet link. Under a strict CSP, its hash is the sha256 in the build's
22
+ // csp-hashes.json, which runProdApp also passes to hostDispatch as
23
+ // deps.cspScriptHashes:
25
24
  //
26
25
  // <script>try{var s=localStorage.getItem("kumiko:theme");
27
26
  // if(s==="dark"||(s==="auto"&&matchMedia("(prefers-color-scheme: dark)").matches))
@@ -94,8 +93,8 @@ function persistPreference(preference) {
94
93
  window.localStorage.setItem(THEME_STORAGE_KEY, preference);
95
94
  }
96
95
  catch {
97
- // skip: localStorage kann werfen (Private-Mode/Quota) — Theme bleibt
98
- // dann sessionbasiert, der Class-Toggle hat trotzdem funktioniert.
96
+ // skip: localStorage can throw (private mode/quota); the theme then remains
97
+ // session-based, but the class toggle still works.
99
98
  }
100
99
  }
101
100
  function applyPreference(preference) {
@@ -123,23 +122,22 @@ export function applyStoredThemeMode() {
123
122
  applyPreference(declared);
124
123
  }
125
124
  let storedModeApplied = false;
126
- /** Nur für Tests: der once-per-page-load-Guard ist ein Module-Singleton —
127
- * ohne Reset wäre der Mount-Restore-Pfad nach der ersten Render im
128
- * Testfile strukturell unerreichbar. */
125
+ /** Tests only: the once-per-page-load guard is a module singleton; without a reset,
126
+ * the mount-restore path would be structurally unreachable after the first render
127
+ * in the test file. */
129
128
  export function __resetStoredModeAppliedForTests() {
130
129
  storedModeApplied = false;
131
130
  currentPreference = null;
132
131
  }
133
- /** Hook der eine TokensApi für den Browser baut. Wird von
134
- * createKumikoApp genutzt; App-Code der einen eigenen Token-State
135
- * braucht (z.B. User-Präferenz aus localStorage) kann selber
136
- * `<TokensProvider value={...}>` mounten. */
132
+ /** Builds a TokensApi for the browser. Used by createKumikoApp; app code that needs
133
+ * its own token state (for example, a user preference from localStorage) can mount
134
+ * its own `<TokensProvider value={...}>`. */
137
135
  export function useBrowserTokensApi() {
138
- // Einmal pro Page-Load: gespeicherte Wahl anwenden. Lazy statt
139
- // Modul-Side-Effect, damit Import ohne DOM (SSR/Tests) safe bleibt.
140
- // Als useState-Lazy-Initializer statt nackt im Render-Body: der
141
- // DOM-Side-Effect lief sonst potenziell in einem verworfenen
142
- // Concurrent-Render (React darf Render-Bodies wiederholen/abbrechen).
136
+ // Apply the stored choice once per page load. This is lazy rather than a module
137
+ // side effect so importing without a DOM (SSR/tests) remains safe.
138
+ // A useState lazy initializer is used instead of placing this directly in the
139
+ // render body because the DOM side effect could otherwise run during a discarded
140
+ // concurrent render (React may repeat or abandon render bodies).
143
141
  useState(() => {
144
142
  if (!storedModeApplied && typeof document !== "undefined") {
145
143
  storedModeApplied = true;
@@ -192,13 +190,13 @@ export function useBrowserTokensApi() {
192
190
  },
193
191
  };
194
192
  }
195
- /** Default-Tokens — identisch zu `cssVarTokens` (var-string-refs).
196
- * Light- und Dark-Werte switchen via `.dark`-class auf <html>. */
193
+ /** Default tokens—identical to `cssVarTokens` (variable-string references).
194
+ * Light and dark values switch via the `.dark` class on <html>. */
197
195
  export const defaultTokens = cssVarTokens;
198
196
  export const lightTokens = cssVarTokens;
199
- /** Historisch: schrieb Tokens auf :root als CSS-vars. Jetzt no-op —
200
- * die CSS-vars leben in styles.css, nicht in JS. Bleibt als Export
201
- * damit alter App-Code nicht bricht. */
197
+ /** Historically wrote tokens to :root as CSS variables. Now a no-op—the CSS
198
+ * variables live in styles.css, not JS. Retained as an export so existing app code
199
+ * does not break. */
202
200
  export function applyTokensToCssVars(_tokens) {
203
- // Absichtlich leer — siehe Kommentar oben.
201
+ // Intentionally empty—see the comment above.
204
202
  }
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Update-Awareness — default an, kein Flag. Ein offener Tab erfährt sonst
3
- // nie von einem neuen Deploy (SPA bleibt geladen, index.html wird nicht neu
4
- // geholt → "muss hart reloaden"). Statt Service-Worker (der ist die *Ursache*
5
- // von "hängt auf alter Version") pollt diese Komponente die statische
6
- // build-info.json beim Tab-Fokus und zeigt ein Reload-Banner bei Drift.
2
+ // Update awareness is enabled by default, without a flag. Otherwise, an open tab
3
+ // never learns about a new deploy because the SPA stays loaded and does not fetch
4
+ // index.html again, so it requires a hard reload. Instead of a service worker—which
5
+ // causes the “stuck on old version” problem—this component polls the static
6
+ // build-info.json when the tab gains focus and shows a reload banner on drift.
7
7
  //
8
- // Versionsquelle ist build-info.json (Hash über die Asset-URLs, vom Prod-Build
9
- // geschrieben) — NICHT /api/version (live leer/404, Env-/Dockerfile-Falle).
10
- // Banner-Strings über kumikoDefaultTranslations (kumiko.version.*, de/en).
8
+ // The version source is build-info.json (a hash of the asset URLs written by the
9
+ // production build), not /api/version (which may be empty or 404 at runtime because
10
+ // of environment or Dockerfile configuration). Banner strings come from
11
+ // kumikoDefaultTranslations (kumiko.version.*, de/en).
11
12
  import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
12
13
  import { useEffect, useState } from "react";
13
14
  import { useTranslation } from "../../index.js";
@@ -21,9 +22,9 @@ export function readLoadedBuild() {
21
22
  return undefined;
22
23
  return { id, builtAt: meta?.getAttribute("data-built-at") ?? "" };
23
24
  }
24
- // Korrektheitsgrenze der Update-Erkennung: ein Banner NUR bei echtem
25
- // ID-Drift. Kein geladener Stand (Dev/altes Bundle) oder kein Server-Stand
26
- // (Fetch-Fehler/kaputtes JSON → null) → nie ein Fake-Banner.
25
+ // Update detection is only valid for a real ID drift: a banner is shown only then.
26
+ // No loaded build (development or an old bundle), or no server build (fetch failure
27
+ // or invalid JSON → null), must never produce a false update banner.
27
28
  export function shouldShowUpdate(loadedId, server) {
28
29
  if (!loadedId || !server)
29
30
  return false;
@@ -44,7 +45,7 @@ async function fetchServerBuild() {
44
45
  return { id: raw.id, builtAt: typeof builtAt === "string" ? builtAt : "" };
45
46
  }
46
47
  catch {
47
- // Netzwerkfehler / kaputtes JSON → kein Banner. Nie ein Fake-Update zeigen.
48
+ // Network failure or invalid JSON → no banner; never show a false update.
48
49
  return null;
49
50
  }
50
51
  }
@@ -55,7 +56,7 @@ export function UpdateChecker() {
55
56
  useEffect(() => {
56
57
  const loadedId = loaded?.id;
57
58
  if (!loadedId)
58
- return; // Dev / altes Bundle → keine Awareness.
59
+ return; // Development or an old bundle → no update awareness.
59
60
  let cancelled = false;
60
61
  const checking = { current: false };
61
62
  const check = async () => {
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // AiTextField / AiTextArea — Drop-in replacement for TextField/TextareaField
3
- // with AI-augmented actions (ai-text-primitive plan doc, Phase 3).
3
+ // with AI-augmented actions (from the ai-text-primitive plan).
4
4
  //
5
5
  // Built on raw <input>/<textarea> (not the generic Input primitive) because
6
6
  // ghost-text needs a keydown handler (Tab/Esc) and an overlay that the
@@ -1 +1 @@
1
- export declare function createBoundedLoadCache<T>(max: number, dispose: (value: T) => void): (key: string, load: () => Promise<T>) => Promise<T>;
1
+ export declare function createBoundedLoadCache<T>(max: number, dispose: (value: T) => void, shouldKeep?: (value: T) => boolean): (key: string, load: () => Promise<T>) => Promise<T>;